Laravel Events and Listeners in 2026: Event-Driven Architecture and Interview Questions

Master Laravel events and listeners with automatic discovery, queued listeners, and event subscribers. Covers Laravel 12 patterns and common interview questions.

Laravel events and listeners event-driven architecture diagram

Laravel events and listeners implement the observer pattern, decoupling components by letting parts of an application react to actions without direct dependencies. When a user places an order, the OrderShipped event fires, and separate listeners handle notifications, analytics, and inventory updates independently.

Laravel 12 Event Discovery

Laravel 12 automatically discovers listeners in app/Listeners. Any class with a handle or __invoke method type-hinting an event class gets registered automatically, no configuration file needed.

How Laravel Event Discovery Works in Version 12

Since Laravel 11, the EventServiceProvider is gone. Laravel scans the Listeners directory and uses reflection to determine which events each listener responds to based on the type hint in the handle method signature.

app/Listeners/SendOrderConfirmation.phpphp
<?php

namespace App\Listeners;

use App\Events\OrderShipped;

class SendOrderConfirmation
{
    public function handle(OrderShipped $event): void
    {
        // Send confirmation email to $event->order->user
    }
}

The listener above gets automatically registered for OrderShipped events. No manual binding in a service provider. This reduces boilerplate and keeps event-listener relationships close to the code that implements them.

To verify registered listeners, run php artisan event:list. For production, cache the event manifest with php artisan event:cache to skip directory scanning on each request.

Creating Events and Listeners with Artisan

Laravel provides Artisan commands to scaffold events and listeners:

bash
# Generate an event class
php artisan make:event OrderShipped

# Generate a listener linked to an event
php artisan make:listener SendShipmentNotification --event=OrderShipped

The generated event class uses three traits: Dispatchable for the static dispatch() method, InteractsWithSockets for broadcasting, and SerializesModels to serialize Eloquent models when listeners are queued.

app/Events/OrderShipped.phpphp
<?php

namespace App\Events;

use App\Models\Order;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Queue\SerializesModels;

class OrderShipped
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(
        public Order $order,
    ) {}
}

The event class is a data container. It holds the Order model and nothing else. Business logic belongs in listeners, not events.

Dispatching Events from Controllers and Services

Dispatch events using the static dispatch() method on the event class:

app/Http/Controllers/OrderController.phpphp
<?php

namespace App\Http\Controllers;

use App\Events\OrderShipped;
use App\Models\Order;
use Illuminate\Http\Request;

class OrderController extends Controller
{
    public function ship(Request $request, Order $order)
    {
        // Shipping logic here...
        
        OrderShipped::dispatch($order);
        
        return redirect()->route('orders.index');
    }
}

For conditional dispatch, use dispatchIf() or dispatchUnless():

php
// Only dispatch if the order has a tracking number
OrderShipped::dispatchIf($order->tracking_number !== null, $order);

Ready to ace your Laravel interviews?

Practice with our interactive simulators, flashcards, and technical tests.

Queued Listeners for Slow Operations

Listeners that send emails, call external APIs, or perform heavy computations should run asynchronously. Implement ShouldQueue to push the listener onto the queue:

app/Listeners/SendShipmentNotification.phpphp
<?php

namespace App\Listeners;

use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;

class SendShipmentNotification implements ShouldQueue
{
    public $queue = 'notifications';
    public $delay = 60; // Wait 60 seconds before processing

    public function handle(OrderShipped $event): void
    {
        // Send notification to $event->order->user
    }
}

Customize queue connection and name with $connection and $queue properties, or use runtime methods:

php
public function viaConnection(): string
{
    return 'redis';
}

public function viaQueue(): string
{
    return $this->event->order->priority === 'high' 
        ? 'high-priority' 
        : 'default';
}

Conditional Queueing with shouldQueue

Sometimes a listener should only queue based on event data:

php
public function shouldQueue(OrderShipped $event): bool
{
    // Only queue for orders above $100
    return $event->order->total >= 10000;
}

Database Transactions and Event Timing

Queued listeners dispatched inside a transaction may process before the transaction commits, causing missing data errors. Two solutions exist:

1. Configure globally in config/queue.php:

php
'connections' => [
    'redis' => [
        'after_commit' => true,
    ],
],

2. Per-listener with ShouldQueueAfterCommit:

php
use Illuminate\Contracts\Queue\ShouldQueueAfterCommit;

class UpdateInventory implements ShouldQueueAfterCommit
{
    public function handle(OrderShipped $event): void
    {
        // Safe to query the order, transaction is committed
    }
}

For events themselves, implement ShouldDispatchAfterCommit on the event class:

php
use Illuminate\Contracts\Events\ShouldDispatchAfterCommit;

class OrderShipped implements ShouldDispatchAfterCommit
{
    // Event only dispatches after transaction commits
}

Unique Listeners to Prevent Duplicates

The ShouldBeUnique interface prevents duplicate listeners from queueing while one is already processing:

php
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;

class ProcessLicenseKey implements ShouldQueue, ShouldBeUnique
{
    public $uniqueFor = 3600; // Lock expires after 1 hour

    public function uniqueId(LicensePurchased $event): string
    {
        return 'license:' . $event->license->id;
    }

    public function handle(LicensePurchased $event): void
    {
        // Generate and assign license key
    }
}

If the same license ID triggers multiple events quickly, only the first listener processes. Subsequent attempts find the lock held and skip queueing.

When multiple related events share common handling logic, event subscribers group handlers in a single class:

app/Listeners/UserActivitySubscriber.phpphp
<?php

namespace App\Listeners;

use Illuminate\Auth\Events\Login;
use Illuminate\Auth\Events\Logout;
use Illuminate\Events\Dispatcher;

class UserActivitySubscriber
{
    public function handleLogin(Login $event): void
    {
        // Log successful login, update last_login timestamp
    }

    public function handleLogout(Logout $event): void
    {
        // Log logout, clear session data
    }

    public function subscribe(Dispatcher $events): array
    {
        return [
            Login::class => 'handleLogin',
            Logout::class => 'handleLogout',
        ];
    }
}

Subscribers follow automatic discovery rules. If handler methods are in the subscriber class with properly type-hinted parameters, Laravel registers them automatically.

Listening to Multiple Events with Union Types

A single listener can respond to multiple event types using PHP 8 union types:

php
public function handle(OrderShipped|OrderCancelled $event): void
{
    match (true) {
        $event instanceof OrderShipped => $this->notifyShipped($event),
        $event instanceof OrderCancelled => $this->notifyCancelled($event),
    };
}

This pattern works well for notifications that follow similar logic across different event types.

Deferring Events with Event::defer

Laravel 12 introduced Event::defer() to delay event dispatch until after a code block completes. This ensures listeners have access to all related records:

php
use Illuminate\Support\Facades\Event;

Event::defer(function () {
    $user = User::create(['name' => 'John']);
    $user->posts()->create(['title' => 'Welcome']);
});
// Events for both User and Post creation dispatch here

If an exception occurs inside the closure, deferred events never dispatch.

Start practicing!

Test your knowledge with our interview simulators and technical tests.

Interview Questions on Laravel Events and Listeners

Technical interviews frequently test understanding of event-driven architecture. Here are questions that distinguish experienced Laravel developers:

Q: How does Laravel 12 register event listeners without EventServiceProvider?

Laravel scans app/Listeners using reflection. It reads the type hint on handle() or __invoke() methods to determine which event each listener responds to. Cache the manifest in production with php artisan event:cache.

Q: When should a listener implement ShouldQueue?

Whenever the operation takes more than a few milliseconds: sending emails, calling external APIs, generating reports, or processing files. Synchronous listeners block the HTTP response.

Q: What problem does ShouldQueueAfterCommit solve?

Queued listeners may process before the database transaction commits. If the listener queries for a just-created record, it fails because the data is not yet committed. ShouldQueueAfterCommit delays queue dispatch until the transaction completes.

Q: How do you prevent duplicate processing for the same event?

Implement ShouldBeUnique. Define uniqueId() to return a cache key based on event data. The listener acquires an atomic lock, and duplicate dispatches skip if the lock is held.

Q: What is the difference between an event listener and an event subscriber?

A listener handles one event type. A subscriber is a class that registers handlers for multiple related events in a single place, useful for grouping authentication events or audit logging.

What Laravel Events Mean for Scalable Applications

  • Events decouple components, letting features like notifications and analytics evolve independently
  • Queued listeners offload slow operations, keeping HTTP responses fast
  • ShouldQueueAfterCommit prevents race conditions between queue workers and database transactions
  • Automatic discovery in Laravel 12 removes configuration overhead, keeping listener logic close to implementation
  • Event subscribers consolidate related event handling, reducing file sprawl for common patterns like user activity tracking
  • The /technologies/laravel/interview-questions/events-listeners module on SharpSkill covers these patterns with interactive questions
Daily challenge

Can you spot the bug in Laravel?

One real snippet, one hidden bug, one attempt a day. No account needed to try.

Anthony Fillion-Maillet

Written by

Anthony Fillion-Maillet

Founder of SharpSkill

Full-stack developer for over 10 years. Runs SharpSkill and answers for everything published here.

Updated on August 20, 2026

Tags

#laravel
#events
#listeners
#php
#event-driven
#queues

Share

Related articles