$ lexprog.com

// notes from an old coder -- php, databases, and the occasional rant

[April 15, 2025] Laravel

Laravel Events and Listeners

Laravel Events and Listeners: Tips & Tricks

────────────────────────────────────────────────────────

Laravel Events and Listeners: Tips & Tricks

Tip: Use Event Subscribers for Related Listeners

Instead of registering multiple listeners, use a subscriber:

class UserEventSubscriber
{
    public function handleLogin($event) { /* ... */ }
    public function handleLogout($event) { /* ... */ }

    public function subscribe(Dispatcher $events): void
    {
        $events->listen(UserLoggedIn::class, [self::class, 'handleLogin']);
        $events->listen(UserLoggedOut::class, [self::class, 'handleLogout']);
    }
}

Gotcha: Queued Listeners Run Asynchronously

If a listener implements ShouldQueue, it runs in the background. Errors won't bubble up to the original request.

Tip: Use Closures for Simple Listeners

Event::listen(function (OrderShipped $event) {
    Mail::to($event->order->user)->send(new ShippedMail($event->order));
});

No need for a full listener class for one-liners.

Gotcha: Event::fake() Breaks Model Events

When you call Event::fake() in tests, it also fakes model events (creating, saved, etc.). Use Event::fakeExcept() to exclude them.

Tip: Wildcard Listeners

Event::listen('user.*', function ($eventName, array $data) {
    Log::info("User event: {$eventName}");
});

Catches all events starting with user..

Tip: Use withoutEvents() for Bulk Operations

User::withoutEvents(function () {
    User::query()->update(['active' => true]);
});

Skips all model events during the operation. Great for migrations and bulk updates.

Gotcha: Event Order is Not Guaranteed

Unless listeners are queued, they run in registration order. But queued listeners can execute in any order.

Tip: Use route:cache Carefully

php artisan route:cache is fast, but it doesn't work with closure-based routes. Every time you cache routes, Laravel serializes them. If you have Route::redirect() or closure callbacks, the cache breaks. Stick to controller-based routes in production.

Tip: Model APP_KEY Rotation

Rotating APP_KEY invalidates all encrypted data — cookies, encrypted DB columns, and password reset tokens. If you must rotate (e.g., after a leak), plan a migration that re-encrypts existing data with the new key.

Gotcha: Local Scope Leaks

Global scopes defined in booted() apply to ALL queries on that model — including relationships. An innocent User::all() in admin panel might exclude soft-deleted users if a global scope is active.

Senior Insight

Event-driven architecture in Laravel is powerful but easy to misuse. The pitfall I see most often is over-using events for things that should be direct method calls — every event adds indirection and makes the code harder to trace. My rule: use events only when the listener belongs to a different domain or when multiple unrelated listeners need to react. For everything else, just call the method directly. Your future self will thank you when debugging.

Source: Laravel News (https://laravel-news.com/), Freek.dev (https://freek.dev/tags/laravel), Spatie Blog (https://spatie.be/blog)

────────────────────────────────────────────────────────
<-- back to posts