yii模型的scenario仅控制rules()中验证规则的触发条件,不负责字段过滤或权限控制;必须在load()或validate()前设置,且需配合safe/unsafe规则或空场景加载来实现数据安全。

场景(scenario)不是用来分组字段的
Yii 模型里的 scenario 本质是验证规则的开关集合,不是字段白名单或数据过滤器。设了 $model->scenario = 'create',不会自动剔除其他字段,也不会阻止你赋值 $model->updated_at —— 它只决定哪些 rules() 条目会被触发。
- 常见错误:以为设了
'scenario' => ['update']就能防止用户提交created_at字段,结果数据库被恶意覆盖 - 真正该配合用的是
safe和unsafe验证规则,或者显式调用$model->load($data, '')(空字符串禁用场景)再手动赋值 -
rules()中每条规则末尾的['on' => ['create']]才是生效条件,不写on就默认所有场景都跑
怎么定义和切换场景才不踩坑
场景名是纯字符串,Yii 不校验是否存在,拼错就等于没生效;而且切换必须在 load() 或 validate() 之前完成。
- 定义场景推荐在模型类顶部加常量:
const SCENARIO_CREATE = 'create';,避免散落字符串导致 typo - 切换时机很关键:在
$model->load($post)后改scenario是无效的,因为load()内部已根据当时场景生成了安全属性列表 - API 场景建议按动词 + 实体命名,比如
'api/update-profile',避免和控制器动作名冲突(如'update'可能被多个 action 复用) - 不要在
beforeValidate()里动态改scenario,验证流程已启动,规则早已筛选完毕
rules() 里 on / except 的参数差异直接影响验证范围
on 和 except 控制的是“这条规则是否参与当前场景”,不是“只在这几个场景运行”——它们互斥,不能同时出现。
-
[['email'], 'email', 'on' => ['register', 'reset-password']]:仅当scenario是这两个之一时,才校验邮箱格式 -
[['status'], 'in', 'range' => [0,1], 'except' => ['create']]:除了create场景,其他都校验 status 是否为 0/1 - 漏写
on或except的规则会无条件执行,容易在 API 更新时意外触发旧验证(比如给update场景加了密码必填,但忘了限制on) - 复合场景如
'on' => ['api/create', 'admin/create']是合法的,但注意字符串要完全匹配,'api/create'≠'apicreate'
验证通过后,场景对后续操作基本没影响
一旦 validate() 返回 true,scenario 的使命就结束了。它不会影响 save() 行为、不会限制属性可写性、也不参与 AR 查询。
- 别指望设了
scenario = 'admin'就能绕过用户权限检查——那是业务逻辑,得自己在beforeSave()或 service 层处理 -
save(false)会跳过验证,此时scenario完全无关;而save(true)会重新触发验证,用当前scenario - 批量赋值(
load())和单字段赋值($model->name = 'xxx')都不受scenario影响,只有验证和安全属性判定阶段才读取它
场景机制本身很轻,但容易误以为它承担了字段过滤、权限控制、流程分支等职责。其实它只管“哪几条规则该跑”,其余都得靠你手动补位。










