Laravel中save()/update()字段变更检测及自动化记录方法咨询
首先来确认你的第一个疑问:
1. ->save() 和 ->update() 是否仅在数据变更时执行更新?
是的,完全正确!从你贴出的Illuminate\Database\Eloquent\Model@performUpdate代码就能看出来:
protected function performUpdate(Builder $query, array $options = []) {
$dirty = $this->getDirty();
if (count($dirty) > 0) {
// runs update query
}
return true;
}
不管是调用save()(当模型是已存在的实例时)还是update(),最终都会走到这个performUpdate方法。它会先通过getDirty()获取所有和原始数据不一致的字段(也就是"脏数据"),只有当脏数据数组不为空时,才会触发实际的数据库更新查询。如果数据没有任何变化,这两个方法都不会执行SQL更新操作,直接返回true。
2. 关于你的常规更新写法
你当前的更新写法是Laravel中非常标准且推荐的方式:
public function update(Requests\UpdatePetRequest $request, Pet $pet) { $pet->update($request->all()); return $pet; }
这种写法简洁安全(前提是你的UpdatePetRequest已经做了字段验证和授权),完全没问题。
3. 自动获取更新字段、旧值与新值的自动化方案
你不需要手动判断每个字段的变化,Laravel Eloquent已经提供了内置的方法来获取这些信息,结合模型事件或观察者就能实现完全自动化的追踪:
方案一:利用模型的updating事件
在你的Pet模型中,通过boot方法注册updating事件,这个事件会在模型更新操作执行之前触发,此时可以轻松拿到所有变更信息:
namespace App\Models; use Illuminate\Database\Eloquent\Model; class Pet extends Model { // ... 你的模型属性和其他代码 protected static function boot() { parent::boot(); static::updating(function (Pet $pet) { // 获取所有变更的字段(键是字段名,值是新值) $dirtyFields = $pet->getDirty(); if (!empty($dirtyFields)) { foreach ($dirtyFields as $field => $newValue) { // 获取该字段的原始旧值 $oldValue = $pet->getOriginal($field); // 这里可以根据需求处理这些信息:比如写入日志、触发通知等 // 示例:记录到日志文件 logger()->info("宠物[ID: {$pet->id}]的字段{$field}已更新", [ '旧值' => $oldValue, '新值' => $newValue ]); } } }); } }
方案二:使用模型观察者(适合多模型复用场景)
如果你的多个模型都需要追踪字段变更,使用观察者会更优雅,便于统一管理:
- 生成观察者:
php artisan make:observer PetObserver --model=Pet
- 在观察者中实现
updating方法:
namespace App\Observers; use App\Models\Pet; use Illuminate\Support\Facades\Log; class PetObserver { public function updating(Pet $pet) { $dirty = $pet->getDirty(); if (count($dirty) === 0) { return; // 没有变更直接返回 } foreach ($dirty as $field => $newValue) { $oldValue = $pet->getOriginal($field); // 这里可以将变更信息存入专门的日志表,或者做其他业务处理 Log::channel('model_changes')->info("Pet模型更新", [ 'model_id' => $pet->id, 'field' => $field, 'old_value' => $oldValue, 'new_value' => $newValue ]); } } }
- 在
AppServiceProvider中注册观察者:
namespace App\Providers; use App\Models\Pet; use App\Observers\PetObserver; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { public function boot() { Pet::observe(PetObserver::class); } }
方案三:自定义Trait封装变更获取逻辑
如果只是需要在控制器中手动获取变更信息(而不是自动触发处理),可以封装一个Trait:
namespace App\Traits; trait TracksChanges { /** * 获取模型的变更日志(字段名、旧值、新值) * @return array */ public function getChangeLog(): array { $changeLog = []; $dirtyFields = $this->getDirty(); foreach ($dirtyFields as $field => $newValue) { $changeLog[] = [ 'field' => $field, 'old_value' => $this->getOriginal($field), 'new_value' => $newValue ]; } return $changeLog; } }
然后在Pet模型中引入这个Trait:
namespace App\Models; use App\Traits\TracksChanges; use Illuminate\Database\Eloquent\Model; class Pet extends Model { use TracksChanges; // ... }
之后在控制器中就能直接调用:
public function update(Requests\UpdatePetRequest $request, Pet $pet) { $pet->update($request->all()); // 获取变更信息 $changes = $pet->getChangeLog(); // 处理变更信息,比如返回给前端或者存入数据库 return response()->json([ 'pet' => $pet, 'changes' => $changes ]); }
以上三种方案都能完美替代你的手动判断实现,完全利用Laravel的内置能力,不需要额外开发复杂逻辑。
内容的提问来源于stack exchange,提问作者Blagoh

