oldAttributes为null是因为模型未通过findOne()等方法从数据库加载,导致无原始数据快照;正确做法是先查询再更新,并用save()配合beforeSave/afterSave钩子获取新旧值对比。

update() 时 oldAttributes 为什么是 null
调用 $model->update() 前没先查出模型实例,$model->oldAttributes 就一定是 null。因为 oldAttributes 是 ActiveRecord 从数据库加载时自动保存的快照,不是靠“当前值 vs 修改后值”动态算出来的。
常见错误写法:$model = new User(); $model->id = 123; $model->status = 2; $model->update();
这行不通——$model 是新实例,没加载过数据,oldAttributes 为空,且 update() 会直接报错(缺少主键条件或找不到记录)。
- 正确路径必须是:先
findOne()或find()->one()加载已有记录 -
update()只接受属性变更数组,不走模型生命周期,也不触发beforeSave/afterSave - 想拿旧值?只能在
findOne()后立刻读$model->oldAttributes,之后再改属性
save() 里怎么安全拿到修改前后的字段值
真正需要对比新旧值的场景(比如审计日志、条件判断),应该用 save() 配合钩子函数,而不是手动调 update()。
beforeSave() 和 afterSave() 是唯二能稳定访问新旧状态的地方:
-
beforeSave($insert)中:$this->oldAttributes是完整旧值,$this->attributes是待保存的新值(含未 dirty 的字段) -
afterSave($insert, $changedAttributes)中:$changedAttributes是真正被改过的字段名+旧值映射,例如['status' => 0];$this->attributes是新值,$this->oldAttributes仍可用但已和数据库一致 - 注意:
$changedAttributes在新增时为空数组,别直接 foreach 它而没判空
getDirtyAttributes() 和 getOldAttributes() 的实际差异
getDirtyAttributes() 返回的是「本次 save() 实际会写入数据库的字段 + 当前值」,而 getOldAttributes() 是「加载时数据库里的原始值」——两者字段集可能不同。
- 如果只改了
name,getDirtyAttributes()返回['name' => 'new'];getOldAttributes()返回全部字段,如['id'=>1,'name'=>'old','email'=>'x@y.z'] -
getDirtyAttributes('name')可指定单字段,返回['name' => 'new']或null(未改动) - 若模型刚
load()但没改任何字段,getDirtyAttributes()返回空数组,getOldAttributes()仍有值 - 软删除字段(如
deleted_at)被行为自动设置时,也会进dirty列表
用 DAO 批量更新时 oldAttributes 完全不可用
一旦绕开 ActiveRecord 直接用 createCommand()->update() 或原生 SQL,oldAttributes、getDirtyAttributes() 全部失效——因为没模型实例参与。
- DAO 更新不加载数据,不触发任何事件,不维护
_oldAttributes内部状态 - 想审计批量更新?得自己先
SELECT出旧值,再UPDATE,并用事务包住 - 若只是简单设字段(如全员 status=2),DAO 更快更安全;但凡涉及逻辑判断或旧值依赖,必须回到 AR 流程
最易忽略的一点:即使你写了 $model->setAttributes($data),只要没调 save() 或显式触发 afterFind,oldAttributes 就不会被重置——它只在 findOne()、refresh() 或 save() 成功后更新。











