Yii2中字段能否被load()赋值由scenarios()白名单硬性控制,与rules()的on参数无关;必须显式定义场景并设置$model->scenario,敏感字段不应加入更新场景,前后端权限需同步。

scenarios() 方法决定哪些字段能被 load() 赋值
Yii2 不靠“隐藏字段”或“前端控制”来限制更新范围,而是靠 scenarios() 返回的字段白名单硬性过滤。如果某个字段没出现在当前 scenario 对应的数组里,load() 就会直接丢弃它,连验证都不走。
常见错误现象:$model->load($post) 返回 true,但数据库里某些字段始终没变——八成是该字段没放进当前场景的 scenarios() 列表里。
-
scenarios()必须显式重写,不能依赖父类默认逻辑;哪怕只改一个场景,也要完整返回所有需支持的场景映射 - 敏感字段如
status、created_by、is_admin,除非业务真允许修改,否则别放进任何update场景的字段列表 - 字段名必须和数据库列名/模型属性名完全一致,大小写、下划线都不能错
- 如果用
ActiveRecord::findOne()加载已有记录再修改,scenarios()仍生效——它控制的是后续load()的行为,不是初始化时的数据来源
rules() 中的 on 参数不控制字段赋值,只控制验证触发
rules() 里的 'on' => 'update' 只决定这条验证规则是否跑,不影响字段能不能被 load() 接收。字段能否进模型,只由 scenarios() 决定。
容易踩的坑:有人在 rules() 里写了 [['email'], 'required', 'on' => 'update'],就以为 email 字段在 update 场景下“自动可写”,结果 load() 时它被静默丢弃了——因为 scenarios() 没把它列进去。
- 先确保字段在
scenarios()中存在,再考虑要不要加验证规则 - 同一字段在不同场景下可设不同规则,比如
title在create下必填,在update下可选,但只要它在两个场景的scenarios()里都存在,就能被赋值 - 不要在
rules()里写运行时判断(如if ($this->status === 'draft')),规则数组是静态定义的,运行时条件应放beforeValidate()或自定义验证方法里
控制器中必须手动设置 scenario 才生效
$model->scenario = 'update' 这一步不能省,也不能指望构造函数参数或配置自动传递。Yii2 不会根据动作名(如 actionUpdate)自动推断场景。
典型错误写法:$model = new User(); $model->load($post); ——此时用的是 default 场景,而你很可能根本没定义过它的字段列表,导致行为不可控。
- 新建模型时直接传参:
$model = new User(['scenario' => 'update']); - 已有模型实例用属性赋值:
$model->scenario = 'update';或$model->setScenario('update'); - 调用
load()时第二个参数可指定场景名,但前提是模型已设好scenario,否则无效 - API 场景下若用 JSON 提交,
load($data, '')会绕过 formName 匹配,但仍受scenarios()约束
批量更新或导入时如何安全跳过字段校验
后台批量导入常需忽略头像、简介等非结构化字段,又不能让 load() 因字段缺失报错。这时候不能删规则,而要用 'safe' 配合场景隔离。
例如导入场景叫 import,你想让 avatar 字段不参与任何验证、也不影响其他字段赋值:
- 在
scenarios()中把avatar加入import列表,否则load()直接丢弃 - 在
rules()中加一条:[['avatar'], 'safe', 'on' => 'import']——这表示它可被赋值,但跳过所有验证器 - 千万别写成
[['avatar'], 'required', 'on' => 'import'],否则导入时没传头像就会验证失败 - 如果字段根本不需要存,连
scenarios()都不该放它;放了又'safe',是为了兼容前端可能误传,避免load()报错
最易被忽略的一点:字段权限控制必须前后端同步。仅靠 scenarios() 拦住后端是底线,前端仍要按场景渲染表单,不能只用 CSS display: none 隐藏敏感字段——用户禁用 JS 后仍可提交非法数据。


















