Yii2更新数据时必须显式控制字段过滤,否则非法字段如is_admin=1可能直接写入数据库;核心是正确使用scenario、rules()和load()顺序,漏配会导致安全风险或逻辑错误。

Yii2 更新数据时,多余表单字段不会自动被过滤,必须显式控制——否则 is_admin=1 这类非法字段可能直接写入数据库,哪怕前端没展示、用户没权限。
load() + scenario 是最常用也最容易出错的方式
调用 $model->load() 本身不校验字段合法性,只做键值赋值;真正起过滤作用的是 scenario 和 rules() 的组合。
-
$model->setScenario('update')必须在load()前调用,否则默认用'default'场景,而多数模型的scenarios()里根本没配'default' -
scenarios()中定义哪些字段允许批量赋值(即白名单),例如:return ['update' => ['username', 'email']];漏掉字段就静默丢弃 -
rules()中用on指定场景,并配合only或except进一步收紧,比如:['username', 'required', 'on' => 'update'] - 常见错误:前端传了
id和created_at,但scenarios()没放开id,导致save()时主键条件丢失,变成插入新记录
save() 时指定字段列表可绕过 scenario 但风险更高
直接写 $model->save(true, ['username', 'email']) 确实能跳过 scenario 配置,但意味着你把字段控制逻辑从模型层挪到了控制器层,容易散落、遗漏、难维护。
- 这个字段数组必须手写,不能靠
$model->attributes动态取——里面包含id、created_at等不该更新的字段 - 如果某次更新要加一个新字段,你得同时改控制器里的数组和模型的
rules(),两处不同步就出问题 - 它不触发验证规则(除非第一个参数设为
false),所以required、email格式等全失效 - 更隐蔽的问题:关联字段(如
profile.status)无法用这种方式更新,因为不是ActiveRecord的直接属性
API 场景下用 SearchModel 做过滤反而不适用
SearchModel 是为查询(SELECT)设计的,不是为更新服务的。它的 search() 方法返回的是 Query 对象,跟 save() 完全无关。
- 有人误把
UserSearch当成更新入口,结果发现load()成功但save()报错——因为UserSearch不是ActiveRecord子类,没有save()方法 - 如果你需要根据搜索条件批量更新(比如“把所有 status=1 的用户改成 status=2”),应该用
User::updateAll(),而不是塞进SearchModel - 真要动态字段更新,建议封装一个独立方法,比如
updateByParams($id, $params),内部用createCommand()->update()显式拼字段,避免模型层干扰
真正难的不是怎么写,而是每次新增字段或调整权限时,是否同步更新了 scenarios()、rules() 和控制器里的字段白名单——这三个地方一旦不一致,过滤就形同虚设。


















