Laravel 模型用 isDirty() 检测字段是否修改,仅对比加载时原始值与当前值;getChanges() 获取新值,getOriginal() 获取旧值;saved 事件中记录变更最可靠,需判 isDirty() 避免冗余日志。

如何在 Laravel 模型中检测字段是否被修改
直接用 $model->isDirty() 或 $model->isDirty('email')。它不依赖数据库状态,只比对当前属性值和原始加载值(即 $model->getOriginal())。注意:只有模型从数据库查出来后、调用 save() 前的变更才被识别;手动赋值但未触发 dirty 标记(比如绕过 fill() 直接改属性)会导致误判。
-
isDirty()返回布尔值,表示任意字段有变化 -
isDirty(['name', 'email'])可批量检查多个字段 - 若模型是新建的(
$model->exists === false),所有isDirty调用都返回false - 调用
$model->save()后,原始值自动更新,dirty 状态清空
保存前获取具体变更字段与旧值
用 $model->getChanges() 获取当前待保存的字段新值,再配合 $model->getOriginal($key) 拿旧值。这是最轻量、无额外查询的追踪方式,适合日志记录或业务校验。
-
$model->getChanges()返回形如['email' => 'new@example.com']的数组 - 旧值必须用
$model->getOriginal('email'),不能用$model->email(那是当前值) - 如果字段从未被加载(比如 select 时没选该字段),
getOriginal()返回null,需自行处理空值逻辑 - 对 JSON 字段或数组类型,
getOriginal()返回的是反序列化后的 PHP 值,不是原始字符串
监听 saved 事件做变更持久化存档
在 saved 事件里查变更并写入历史表,比用 updating 更可靠——因为 updating 触发时事务可能未提交,且 isDirty() 在 saved 里仍有效(原始值尚未刷新)。
- 在
boot()中注册:static::saved(function ($model) { ... }) - 务必先判断
$model->isDirty(),避免无变更也写日志 - 不要在事件里调用
$model->save(),否则可能死循环 - 历史表建议包含字段:
model_type、model_id、changed_at、changes_json(用json_encode($model->getChanges()))
第三方包 spatie/laravel-activitylog 的实际取舍
它封装了事件监听+多模型支持+UI,但默认会为每次 save 插入一条记录,哪怕没变更;且对软删除模型、自定义主键支持较弱。如果你只需要记录关键字段(如 status、price)、或已有审计表结构,手写 saved 事件更可控。
立即学习“PHP免费学习笔记(深入)”;
- 启用前务必配置
only_dirty为true,否则日志爆炸 - 它的
causer依赖中间件注入,CLI 命令或队列中容易丢失上下文 - 如果历史数据要参与查询(比如“查出所有 price 变更过 3 次的订单”),自建表 + 索引比 JSON 字段更高效
真正麻烦的不是怎么记变更,而是怎么定义“一次有意义的变更”——比如用户连续改 5 次邮箱,你是记 5 条,还是合并成 1 条“从 A 到 E”?这得结合业务规则,Eloquent 本身不替你做判断。



















