laravel通过$original快照对比属性赋值或fill()触发的变更来判断脏字段,isdirty()返回布尔值、getdirty()返回变更字段数组;但直接操作attributes或绕过访问器会导致快照失效,强制类型转换和访问器隐式处理也易引发误判。

模型保存前怎么知道哪些字段被改了
用 $model->isDirty() 或 $model->getDirty(),Laravel 在模型实例上维护了一套原始快照($original),每次赋值都会对比。但注意:这个机制只对通过属性赋值($model->name = 'xxx')或 fill() 触发的变更生效,直接操作 $model->attributes 数组或用 setAttribute() 绕过访问器时可能不更新快照。
-
isDirty()返回布尔值,可传字段名判断单个字段:$user->isDirty('email') -
getDirty()返回关联数组,键是字段名,值是当前新值(仅包含已变字段) - 快照在
save()成功后自动刷新;如果调用fresh()或refresh(),也会重置脏状态 - 批量赋值(
fill())后立即调用getDirty()才能拿到准确结果,延迟调用可能因中间其他赋值干扰判断
为什么 isDirty() 有时返回 false 却实际改了数据
常见于模型启用了强制类型转换($casts)或自定义访问器(getFooAttribute)。比如字段是 json 类型,原始值是 null,你赋值一个空数组 [],PHP 序列化后可能和快照里存的 "[]" 字符串不等价;又或者访问器做了 trim、date 格式化等隐式处理,导致内存中值与快照比对失败。
- 检查
$model->getOriginal('field')和$model->getAttribute('field')的原始输出,确认是否真有差异 - 避免在访问器里修改值再返回——这会让脏检查失效;如需格式化,应在视图或 API 层做,而非访问器
- 对 JSON 字段,考虑用
arraycast 而非json,减少序列化层级干扰
更新时跳过未修改字段(减少 SQL 冗余)
Laravel 默认生成的 UPDATE 语句会包含所有非主键字段,哪怕它们根本没变。想只更新脏字段,得手动构造:
PHP中文网提供Laravel 13.2.0版本下载,Laravel框架 是基于 PHP 8.3+ 的高性能框架,官方推荐通过 Composer 安装。它内置 AI SDK、JSON:API Resources 及原生向量搜索,支持属性驱动开发与队列路由,大幅提升开发效率。相比旧版,13.2.0 优化了缓存 TTL 管理与实时通信,无需 Redis 即可横向扩展。作为现代 Web 开发首选,它兼顾安全与极速体验,助您快速构建企业级应用。
$dirty = $user->getDirty();
if (!empty($dirty)) {
$user->whereKey($user->getKey())->update($dirty);
}
但要注意:这种方式绕过了模型事件(saving、updating)、时间戳自动更新(updated_at)、以及观察者逻辑。
- 如果依赖
updated_at,得手动加:$dirty['updated_at'] = now() - 如果用了软删除,
deleted_at变更不会进getDirty(),需单独判断 - 批量更新多个模型时,
update()不触发单个模型的updated事件,监听不到
测试场景下脏状态总为空怎么办
单元测试里用 factory()->create() 创建模型后立刻调用 isDirty(),大概率返回 false——因为工厂创建后模型已持久化,快照和当前值一致。要模拟“修改后未保存”的状态,必须在创建后显式改动属性,且不能调用 save()。
- 正确姿势:
$user = User::factory()->create(); $user->name = 'new'; dd($user->isDirty('name')); - 错误姿势:
$user = User::factory()->create(['name' => 'new']);—— 这时快照就是 'new',没变化 - 用
make()而非create()可避免写库,更适合纯内存脏检查测试
脏检查本质依赖内存快照的一致性,一旦绕过 Laravel 的属性访问路径(比如直接改 $model->attributes 或用 DB facade 强制更新),快照就和实际数据脱节。这种时候别硬调 isDirty(),该查数据库就查数据库。










