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 class NameParser {
Parsing a full name into components in PHP
string-parsing
arrays
normalization
Intermediate
8 steps
php
<?php namespace App\Services\Checkout;
Validating coupons with Laravel's Pipeline
pipeline
chain of responsibility
transactions
Intermediate
7 steps
php
<?php namespace App\Services;
How a password strength validator works in PHP
validation
regular-expressions
data-driven
Intermediate
8 steps
php
<?php namespace App\Services;
Building a cached daily leaderboard in Laravel
caching
aggregation
eager-loading
Intermediate
9 steps
php
<?php final class RotatingFileLogger {
How a rotating file logger works in PHP
logging
file-rotation
io
Intermediate
9 steps
php
class TwoFactorController extends Controller { public function show(Request $request): View|RedirectResponse {
Two-factor auth challenge flow in Laravel
authentication
two-factor
middleware
Intermediate
10 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.