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
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Eloquent's boot{TraitName} convention lets a trait register model event listeners automatically when the model boots.
- 2Comparing getChanges against getOriginal captures exactly what a field changed from and to, without hand-written diffing.
- 3Filtering sensitive keys in one place keeps secrets like passwords out of every audit record consistently.
Related explainers
php
<?php namespace App\Http\Controllers;
Email confirmation with signed URLs in Laravel
signed-urls
email-verification
middleware
Intermediate
6 steps
php
<?php namespace App\RateLimiting;
Sliding-window rate limiting with Redis sorted sets
rate-limiting
redis
sorted-sets
Advanced
8 steps
php
<?php namespace App\Http\Controllers;
Server-Sent Events in Laravel
server-sent-events
streaming
long-polling
Advanced
9 steps
php
<?php namespace App\Actions\Imports;
Streaming CSV imports with Laravel batches
lazy-collections
job-batching
streaming
Advanced
9 steps
php
namespace App\Providers; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Log;
Catching N+1 queries with Eloquent strict mode in Laravel
n+1 queries
strict mode
eager loading
Intermediate
7 steps
php
<?php namespace App\Support;
Recursively finding files with SPL iterators in PHP
recursion
iterators
filesystem
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/how-an-auditable-trait-logs-eloquent-changes-in-laravel-explained-php-784d/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.