updateAttributes() 从不触发验证,是设计使然;它绕过 ActiveRecord 生命周期直写数据库,不校验、不触发事件、不更新时间戳、不尊重软删除。

直接说结论:updateAttributes() 从不触发验证,这是它设计上的明确行为,不是 bug,也不是配置问题。 它只做一件事:绕过所有模型层契约,直写数据库字段。想验证就别用它。
为什么 updateAttributes() 不校验也不触发事件
这个方法本质是 DAO 层封装,内部调用的是 createCommand()->update(),和手写 SQL 更新没区别。它跳过整个 ActiveRecord 生命周期——不运行 beforeValidate、不检查 rules()、不调 beforeSave、不更新时间戳、也不管 deleted_at 软删除状态。
- 常见错误现象:调了
$user->updateAttributes(['status' => 2]),结果 status 改了,但updated_at没变,软删除记录也被误更新 - 适用场景:后台脚本批量修正脏数据、运维紧急修复、或你 100% 确认字段值已合法且无需业务干预
- 性能影响:比
save()快,因为不加载验证器、不构造 validator 对象、不遍历 rules 数组
想更新+验证,必须用 save(false) + 手动赋值
如果你既要改字段又要走验证流程,正确路径是:先赋值,再显式调 save(false)(false 表示跳过验证),但前提是——你已经手动确保属性值合法,或自己调过 validate()。
- 不要写
$model->name = 'xxx'; $model->updateAttributes(['name' => 'xxx'])—— 这是重复操作,且后者仍无验证 - 正确写法:
$model->name = 'xxx'; if ($model->validate()) { $model->save(false); } - 注意:如果模型是从
findOne()加载的,save(false)会执行 UPDATE;如果是 new 出来的空模型,save(false)会执行 INSERT - 时间戳行为不会自动触发,得自己设:
$model->updated_at = date('Y-m-d H:i:s');
批量更新且需验证?别硬扛,拆逻辑
单条记录要验证,就用 save();真要批量处理又不能跳过校验,说明你业务上其实不该“批量”——得逐条走流程,否则错误定位和事务回滚都成问题。
- 错误做法:
User::find()->where(['id' => $ids])->all()拉出一堆模型再循环save()—— 内存爆、N+1、慢 - 推荐做法:先用 DAO 查出需要校验的原始数据(如
SELECT id, name FROM user WHERE id IN (...)),在 PHP 层做规则判断,把合法 ID 提取出来,最后用 DAO 批量更新这些 ID - 或者:用事务包裹循环
save(),并在 catch 里记录失败 ID 和错误原因,便于重试 - 关键点:验证和持久化必须分离,不能指望一个方法同时干两件事
最易被忽略的是软删除条件。用 updateAttributes() 更新时,WHERE 子句默认不加 deleted_at IS NULL,逻辑删除的记录也会被扫进来——这比验证失效更危险,因为它直接破坏数据一致性。


















