Eloquent Observers in Practice
Eloquent Observers: Tips & Tricks
Eloquent Observers: Tips & Tricks
Tip: One Observer Per Model
Keep observers focused. One observer class handles all events for one model.
php artisan make:observer PostObserver --model=Post
Gotcha: Observers Run on Every Instance
Every time a model is created, updated, or deleted, the observer fires. For bulk operations, use withoutEvents().
Tip: Use wasChanged() in Updated Events
public function updated(Post $post): void
{
if ($post->wasChanged('status')) {
// Status changed
}
}
Gotcha: Observer Registration
In Laravel 11+, observers are auto-discovered if named {Model}Observer. Otherwise, register manually:
Post::observe(PostObserver::class);
Tip: creating vs created
public function creating(Post $post): void
{
// Before save — modify attributes
$post->slug = Str::slug($post->title);
}
public function created(Post $post): void
{
// After save — side effects
Notification::send($post->author, new PostPublished($post));
}
Gotcha: Observers and Transactions
If your observer sends an email and the transaction rolls back, the email was already sent.
Tip: Silent Operations
$post->updateWithoutTouching(['title' => 'New Title']);
// Doesn't fire updated event, doesn't touch updated_at
Tip: Multiple Models, One Observer
class ContentObserver
{
public function creating(Model $model): void
{
$model->slug = Str::slug($model->title ?? $model->name);
}
}
Post::observe(ContentObserver::class);
Page::observe(ContentObserver::class);
Tip: Use cursor() for Memory-Neutral Iteration
When exporting 100K rows, get() loads everything into memory. cursor() uses yield and keeps memory flat regardless of row count. Perfect for artisan commands.
Tip: whereHas() vs load() — Two Different Things
whereHas() filters the parent query by relationship existence. load() eager-loads relationships AFTER the query. Mixing them up is a common source of logic bugs.
Gotcha: withCount() Adds a Subquery
withCount('comments') runs a correlated subquery on every row. On large tables, this can be slower than a separate query. Profile before relying on it.
Senior Insight
Observers are a clean way to organize model event logic, but they create a problem: model events become invisible in the controller flow. I've seen developers add a created observer without realizing it, then spend hours debugging why extra emails were sent on user registration. My practice: keep observer logic minimal (logging, cache invalidation) and put side-effect-heavy operations in dedicated services or queued listeners where the flow is explicit.
Source: Laravel Docs (https://laravel.com/docs/eloquent), Laravel News (https://laravel-news.com/), Freek.dev (https://freek.dev/tags/eloquent)