php 64 lines · 8 steps

How an Auditable trait logs Eloquent changes in Laravel

A reusable Laravel trait hooks model lifecycle events to write an audit trail of every create, update, and delete.

Explained by highlit
1<?php
2 
3namespace App\Models\Concerns;
4 
5use App\Models\AuditLog;
6use Illuminate\Database\Eloquent\Model;
7use Illuminate\Support\Facades\Auth;
8 
9trait Auditable
10{
11 public static function bootAuditable(): void
12 {
13 static::created(fn (Model $model) => $model->recordAudit('created', [], $model->auditableAttributes()));
14 
15 static::updated(function (Model $model) {
16 $changes = $model->getChanges();
17 
18 unset($changes['updated_at']);
19 
20 if (empty($changes)) {
21 return;
22 }
23 
24 $original = array_intersect_key($model->getOriginal(), $changes);
25 
26 $model->recordAudit('updated', $original, $changes);
27 });
28 
29 static::deleted(fn (Model $model) => $model->recordAudit('deleted', $model->auditableAttributes(), []));
30 }
31 
32 protected function auditableAttributes(): array
33 {
34 return array_diff_key(
35 $this->attributesToArray(),
36 array_flip($this->auditExcluded())
37 );
38 }
39 
40 protected function auditExcluded(): array
41 {
42 return property_exists($this, 'auditExclude')
43 ? array_merge(['password', 'remember_token'], $this->auditExclude)
44 : ['password', 'remember_token'];
45 }
46 
47 protected function recordAudit(string $event, array $old, array $new): void
48 {
49 AuditLog::create([
50 'auditable_type' => $this->getMorphClass(),
51 'auditable_id' => $this->getKey(),
52 'event' => $event,
53 'old_values' => array_diff_key($old, array_flip($this->auditExcluded())),
54 'new_values' => array_diff_key($new, array_flip($this->auditExcluded())),
55 'user_id' => Auth::id(),
56 'ip_address' => request()->ip(),
57 ]);
58 }
59 
60 public function audits()
61 {
62 return $this->morphMany(AuditLog::class, 'auditable')->latest();
63 }
64}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Eloquent's boot{TraitName} convention lets a trait register model event listeners automatically when the model boots.
  2. 2Comparing getChanges against getOriginal captures exactly what a field changed from and to, without hand-written diffing.
  3. 3Filtering sensitive keys in one place keeps secrets like passwords out of every audit record consistently.

Related explainers

Share this explainer

Here's the card — post it anywhere.

How an Auditable trait logs Eloquent changes in Laravel — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code