laravel 中应使用 isdirty() 和 getdirty() 判断字段是否真实修改,因其基于原始快照 $original,不受类型转换或访问器干扰;saved 事件中用 waschanged() 或 getoriginal() 获取准确变更。

监听模型属性是否被修改用 isDirty() 和 getDirty()
模型保存前判断字段有没有真改过,不能只靠对比新旧值——Laravel 的 isDirty() 才是可靠依据。它基于模型内部的原始快照($original)做比对,不受类型转换、访问器或手动赋值干扰。
-
isDirty()返回布尔值,可传字段名精确判断单个字段,比如isDirty('status') -
getDirty()返回键值对数组,只含「本次修改且与原始值不同」的字段,比如['title' => '新标题', 'updated_at' => '2024-05-20']
- 注意:调用
save() 或 <code>update()后,$model->isDirty()会变false;但fill()不会自动触发脏检查,得手动调用syncOriginal()或等下次持久化时才生效 - 如果字段用了
Casts(如array、json),isDirty()默认按引用比较,可能误判;建议在getDirty()后对敏感字段做深比较
在 saved 事件里捕获变更比 saving 更稳妥
想发通知,别在 saving 阶段读 getDirty() ——此时事务还没提交,且部分字段(如 updated_at)可能被框架自动加进去,干扰判断。真正需要的是「已保存成功、且确实有业务字段变了」这个信号。
- 监听
saved事件,在闭包里用$model->wasChanged('field')或$model->getChanges()(等价于getDirty()但语义更明确) -
wasChanged()是实例方法,只在saved/updated等「已完成」事件中有效;saving里调用会返回空数组 - 如果通知逻辑较重(如发邮件、调外部 API),建议 dispatch job 而非同步执行,避免阻塞主请求
- 注意:软删除模型触发
saved时,wasChanged('deleted_at')为true,但你可能不关心这个系统字段——过滤掉更安全
自定义 dirty 判断逻辑要重写 isDirty() 还是用观察者?
有些场景下默认的脏检查不够用:比如字段值没变但关联关系变了(如多对多标签更新),或者想把两个字段组合起来当一个“逻辑字段”看待。这时候硬改 isDirty() 很危险,容易破坏框架行为。
- 优先用观察者(
Observer)配合手动标记:在updating里提前查关联变化,设个临时属性如$model->tags_changed = true,然后在updated里读它 - 不要覆盖模型的
isDirty()方法——它被 Eloquent 多处依赖,比如批量更新、增量更新逻辑都会出错 - 如果必须扩展,可用 trait 封装辅助方法,例如
hasRelatedChanges('tags'),内部用$model->getRelationValue('tags')->wasChanged()或查 pivot 表 - 警惕 N+1:在事件里直接调关联数据,容易触发额外查询;建议用
withCount()或提前load()缓存
updated 事件里拿不到原始值?用 $model->getOriginal('field')
很多人以为 updated 事件里能直接读 $model->original['field'],其实不行——$original 属性在保存后已被同步,和当前值一样了。要对比变更前后,必须在事件触发前抓快照。
-
$model->getOriginal('field')是唯一安全方式,它从模型初始化时的快照里取值,不受后续save()影响 - 别用
$model->getAttributes()减去$model->getOriginal()来推导变更——这会漏掉 null/empty 字段、类型转换差异,也不处理 cast - 如果需要完整历史对比(比如审计日志),建议在
updating事件里把getOriginal()克隆一份存在临时属性里,updated再读 - 注意时间字段:
created_at、updated_at在getOriginal()里是字符串,而Carbon实例比较要用eq(),别用==
dirty 机制看着简单,但一碰上访问器、强制类型转换、软删除、关联变更,就容易漏掉关键差异。最稳的路径是:只在 saved 或 updated 里用 wasChanged() 和 getOriginal(),其他时候别信自己写的“相等判断”。











