应使用 load() 而非已废弃的 assign() 进行表单数据绑定,load() 按场景过滤安全字段并返回布尔值;之后必须显式调用 validate() 触发验证,再调用 save() 才能正确保存。

assign() 是 ActiveRecord 的属性赋值,不是 RBAC 的 assign()
看到 assign() 就下意识去查 RBAC 权限分配?这是最常踩的命名冲突坑。Yii2 里有两个完全无关的 assign():ActiveRecord::assign()(已废弃)和 AuthManager::assign()(权限分配)。表单更新数据用的是模型层的批量赋值逻辑,跟 RBAC 没半毛钱关系。
你真正该用的是 load() —— 它才是 Yii2 表单提交后绑定数据的标准入口。别写 $model->assign($_POST),这方法早在 2.0.13 就被标记为废弃,且不校验场景、不触发验证、不处理空值,纯属历史包袱。
-
$model->load($_POST)自动按当前scenario过滤安全字段,并返回布尔值表示是否成功绑定 - 必须显式传入场景名(如
$model->load($_POST, 'update')),否则走默认default场景,可能漏掉关键字段 - 若模型定义了
scenarios()但没覆盖update,则 fallback 到rules()中带on => ['update']的规则
load() 后必须调用 validate() 才能触发规则检查
load() 只负责把数据塞进属性,不校验、不报错、不拦非法输入。很多开发者卡在这一步:表单提交后字段“看起来”变了,但数据库没更新,或错误信息不显示——根本原因是跳过了 validate()。
-
save()内部会自动调用validate(),但仅当$runValidation参数为 true(默认值) - 手动调用时,顺序必须是:
$model->load() → $model->validate() → $model->save(),不能颠倒 - 验证失败时,
$model->errors会存具体字段错误,前端用$form->field($model, 'name')->error()渲染 - 如果只想更新部分字段(比如只改邮箱),建议用
updateAll()绕过模型层,避免加载整行、触发钩子、走验证
批量更新字段时,不要依赖 load() + save() 全量覆盖
用户在编辑页只改了一个下拉框,你却用 load() 把整个 $_POST 塞进去再 save(),风险极大:
- 前端若被篡改,可能提交
is_admin=1等敏感字段,只要它在rules()的safe列表里,就会静默生效 -
save()会更新所有非NULL属性,包括updated_at,但也会把未提交字段(如密码)设为NULL,除非你在rules()里明确排除 - 性能上,ActiveRecord 要先
SELECT整行,再拼UPDATE,万级并发时 DB 压力陡增
更稳的做法是直接走 DAO:Yii::$app->db->createCommand()->update('user', ['status' => 2], ['id' => $id])->execute();。不加载模型、不触发事件、不走验证,只改你要的字段。
更新时主键丢失导致插入新记录的典型原因
明明是更新操作,结果数据库多了一条记录——大概率是主键没传或被重置了。ActiveRecord 判断新增/更新的唯一依据是 getIsNewRecord() 返回值。
- 用
find()->one()查出模型后,$model->id存在,getIsNewRecord()返回 false,save()执行 UPDATE - 若手动 new 一个模型并赋值:
$model = new User(); $model->id = 123; $model->name = 'xxx';,此时getIsNewRecord()仍为 true,save()会执行 INSERT 并忽略id - 修复方式只有两个:
$model->setIsNewRecord(false),或用User::findOne(123)而不是new User() - 循环批量更新时,每次都要确认
$model->isNewRecord === false,否则批量变成批量插入
主键问题往往藏得深:前端没传 ID、JS 动态删了 hidden input、或者模型构造时误设 $model->primaryKey = 0 —— 这些都比逻辑写错更难 debug。


















