Laravel

Laravel – Event Handling

Laravel Events provide a simple way to implement the observer pattern in your application. Events allow different parts of an application to communicate without becoming tightly coupled.

For example, when a customer places an order, your application might need to:

  • Send a confirmation email

  • Record an activity

  • Update analytics

  • Send a notification

  • Process a background job

Instead of placing all these operations inside the order controller, you can dispatch an event and allow independent listeners to handle each task.

A typical event workflow looks like this:

Application Action
       ↓
Dispatch Event
       ↓
Laravel Event System
       ↓
┌──────────────┬──────────────┬──────────────┐
↓              ↓              ↓
Listener 1     Listener 2     Listener 3
↓              ↓              ↓
Email          Activity       Notification

This makes applications easier to maintain and extend.


Events and Listeners

Laravel uses two main classes:

Event

An event represents something that happened.

Examples:

OrderPlaced
UserRegistered
PaymentCompleted
InvoiceCreated

Listener

A listener defines what should happen when an event occurs.

For example:

OrderPlaced
    ↓
SendOrderConfirmation

One event can have multiple listeners.

OrderPlaced
    ├── SendOrderConfirmation
    ├── RecordOrderActivity
    └── NotifyWarehouse

Each listener can perform its own task independently.


Event and Listener Locations

By convention, events are stored in:

app/Events

Listeners are stored in:

app/Listeners

For example:

app/
├── Events/
│   └── OrderPlaced.php
│
└── Listeners/
    ├── SendOrderConfirmation.php
    └── RecordOrderActivity.php

Creating an Event

You can create an event using Artisan:

php artisan make:event OrderPlaced

Laravel creates:

app/Events/OrderPlaced.php

A modern event class can look like this:

<?php

namespace App\Events;

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

class OrderPlaced
{
    use Dispatchable, SerializesModels;

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

The event receives an Order model through its constructor.

This allows listeners to access the order that caused the event.


Understanding Dispatchable

The Dispatchable trait allows the event to be dispatched conveniently.

For example:

OrderPlaced::dispatch($order);

Instead of:

event(new OrderPlaced($order));

Both approaches can be used.


Creating a Listener

Create a listener using Artisan:

php artisan make:listener SendOrderConfirmation --event=OrderPlaced

Laravel creates:

app/Listeners/SendOrderConfirmation.php

A listener can look like this:

<?php

namespace App\Listeners;

use App\Events\OrderPlaced;

class SendOrderConfirmation
{
    public function __construct()
    {
        //
    }

    public function handle(OrderPlaced $event): void
    {
        $order = $event->order;

        // Send confirmation email...
    }
}

The handle() method receives the event instance.

Because the event contains the order:

$event->order

the listener can access all the order information.


Registering Event Listeners

Laravel applications can discover event/listener relationships automatically when event discovery is enabled.

You can also explicitly register event/listener mappings using the application’s event configuration.

For applications that use explicit registration, the mapping can be defined through an event service provider or the application’s current event configuration mechanism.

A typical mapping looks like:

protected $listen = [
    OrderPlaced::class => [
        SendOrderConfirmation::class,
    ],
];

If one event has multiple listeners:

protected $listen = [
    OrderPlaced::class => [
        SendOrderConfirmation::class,
        RecordOrderActivity::class,
        NotifyWarehouse::class,
    ],
];

This means all three listeners respond to the same event.

The exact registration location depends on the Laravel application structure and version. Modern Laravel applications may use automatic event discovery rather than requiring manual registration.


Event Discovery

Laravel can automatically discover listeners by inspecting listener classes.

For example:

public function handle(OrderPlaced $event): void
{
    // Handle event
}

Laravel can determine that SendOrderConfirmation handles OrderPlaced.

You can inspect discovered events and listeners with:

php artisan event:list

This command is useful for verifying which listeners Laravel knows about.


Dispatching an Event

Once the event and listener have been created, you can dispatch the event from a controller, service, job, or other application class.

For example:

use App\Events\OrderPlaced;

OrderPlaced::dispatch($order);

Or:

event(new OrderPlaced($order));

When Laravel dispatches the event, registered listeners are executed.


Complete Event Example

Let’s create a complete order-processing example.

Step 1: Create the Event

Run:

php artisan make:event OrderPlaced

app/Events/OrderPlaced.php

<?php

namespace App\Events;

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

class OrderPlaced
{
    use Dispatchable, SerializesModels;

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

Step 2: Create a Listener

Run:

php artisan make:listener SendOrderConfirmation --event=OrderPlaced

app/Listeners/SendOrderConfirmation.php

<?php

namespace App\Listeners;

use App\Events\OrderPlaced;
use App\Mail\OrderConfirmationMail;
use Illuminate\Support\Facades\Mail;

class SendOrderConfirmation
{
    public function handle(OrderPlaced $event): void
    {
        $order = $event->order;

        Mail::to($order->customer_email)
            ->send(
                new OrderConfirmationMail($order)
            );
    }
}

Now the listener sends a confirmation email whenever an OrderPlaced event occurs.


Step 3: Dispatch the Event

Suppose an order is created in a controller:

<?php

namespace App\Http\Controllers;

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

class OrderController extends Controller
{
    public function store(Request $request)
    {
        $order = Order::create([
            'customer_name' => $request->customer_name,
            'customer_email' => $request->customer_email,
            'total' => $request->total,
        ]);

        OrderPlaced::dispatch($order);

        return response()->json([
            'message' => 'Order placed successfully.',
            'order_id' => $order->id,
        ]);
    }
}

The controller only needs to create the order and dispatch the event.

The email logic is handled separately by the listener.


Multiple Listeners for One Event

One of the biggest advantages of Laravel events is that one event can have multiple listeners.

Suppose we have:

OrderPlaced

We could create:

SendOrderConfirmation
RecordOrderActivity
NotifyWarehouse
UpdateSalesAnalytics

Each listener can perform a separate task.

For example:

RecordOrderActivity

<?php

namespace App\Listeners;

use App\Events\OrderPlaced;
use Illuminate\Support\Facades\Log;

class RecordOrderActivity
{
    public function handle(OrderPlaced $event): void
    {
        Log::info('New order placed.', [
            'order_id' => $event->order->id,
        ]);
    }
}

Event Mapping

protected $listen = [
    OrderPlaced::class => [
        SendOrderConfirmation::class,
        RecordOrderActivity::class,
    ],
];

Now both listeners respond when the event is dispatched.


Queued Event Listeners

Some event tasks can take significant time.

For example:

  • Sending emails

  • Generating reports

  • Processing images

  • Calling external APIs

  • Sending notifications

These operations can be moved to a queue.

A listener can implement ShouldQueue:

<?php

namespace App\Listeners;

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

class SendOrderConfirmation implements ShouldQueue
{
    public function handle(OrderPlaced $event): void
    {
        // Long-running operation
    }
}

When Laravel processes the event, the listener can be handled through the configured queue system.

Start the queue worker with:

php artisan queue:work

This prevents slow operations from unnecessarily delaying the user’s request.


Queued Listener Configuration

Queued listeners can also define useful properties.

For example:

public $tries = 3;

This allows the job to be attempted multiple times.

A listener can also define a timeout:

public $timeout = 120;

For example:

class SendOrderConfirmation implements ShouldQueue
{
    public $tries = 3;

    public $timeout = 120;

    public function handle(OrderPlaced $event): void
    {
        // Process event
    }
}

Queue settings should be selected according to the operation being performed.


Event Subscribers

Laravel also supports event subscribers.

An event subscriber is a class that can subscribe to multiple events.

For example:

<?php

namespace App\Listeners;

class UserEventSubscriber
{
    public function handleUserRegistered($event)
    {
        // Handle registration
    }

    public function handleUserLoggedIn($event)
    {
        // Handle login
    }

    public function subscribe($events): void
    {
        $events->listen(
            'App\Events\UserRegistered',
            [self::class, 'handleUserRegistered']
        );

        $events->listen(
            'Illuminate\Auth\Events\Login',
            [self::class, 'handleUserLoggedIn']
        );
    }
}

Subscribers are useful when several related events are handled by the same class.


Closures as Event Listeners

For small applications or simple events, you can listen for an event using a closure.

For example:

use App\Events\OrderPlaced;
use Illuminate\Support\Facades\Event;

Event::listen(
    OrderPlaced::class,
    function (OrderPlaced $event) {
        logger()->info(
            'Order placed: ' . $event->order->id
        );
    }
);

Closures can be useful for simple application-level event handling.

For larger operations, dedicated listener classes are generally easier to test and maintain.


Conditional Event Handling

A listener can contain application logic to determine whether an action should be performed.

public function handle(OrderPlaced $event): void
{
    if ($event->order->total < 100) {
        return;
    }

    // Process high-value order
}

This allows the listener to respond only when certain conditions are met.


Event Listener Dependency Injection

Listeners are resolved through Laravel’s service container.

This means dependencies can be injected into the listener’s constructor.

For example:

class SendOrderConfirmation
{
    public function __construct(
        protected OrderService $orderService
    ) {
    }

    public function handle(OrderPlaced $event): void
    {
        $this->orderService->process(
            $event->order
        );
    }
}

Laravel automatically resolves the dependency when creating the listener.


Event Naming

Events should generally describe something that has happened.

Good examples:

OrderPlaced
UserRegistered
PaymentCompleted
InvoiceGenerated
PasswordChanged

Avoid names that describe commands when the class represents an event.

For example:

SendOrderEmail

is better represented as a listener action, while:

OrderPlaced

is an event.

A useful pattern is:

Event = What happened
Listener = What should happen next

Event Testing

Events can be tested using Laravel’s event faking functionality.

For example:

use App\Events\OrderPlaced;
use Illuminate\Support\Facades\Event;

Event::fake();

OrderPlaced::dispatch($order);

Event::assertDispatched(OrderPlaced::class);

You can also verify that the event was dispatched with the expected data:

Event::assertDispatched(
    OrderPlaced::class,
    function ($event) use ($order) {
        return $event->order->id === $order->id;
    }
);

This allows event behavior to be tested without executing the actual listeners.


Event Discovery Commands

Laravel provides useful Artisan commands for working with events.

List registered events and listeners:

php artisan event:list

Depending on the application and Laravel version, event-related Artisan commands may also include commands for caching or clearing discovered event mappings.

Use:

php artisan event:list --help

to see the commands and options available in your installed Laravel version.


Event Handling vs Direct Method Calls

Without events, a controller might contain:

$order = Order::create($data);

Mail::to($order->customer_email)
    ->send(new OrderConfirmationMail($order));

Log::info('Order created.');

NotifyWarehouse::send($order);

This creates strong coupling between the order creation process and every additional operation.

With events:

$order = Order::create($data);

OrderPlaced::dispatch($order);

The event system handles the additional work:

OrderPlaced
    │
    ├── SendOrderConfirmation
    ├── RecordOrderActivity
    └── NotifyWarehouse

This separation makes the application easier to extend.


Complete Example

The following example demonstrates an event, listener, controller, and route.

Event

app/Events/OrderPlaced.php

<?php

namespace App\Events;

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

class OrderPlaced
{
    use Dispatchable, SerializesModels;

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

Listener

app/Listeners/RecordOrderActivity.php

<?php

namespace App\Listeners;

use App\Events\OrderPlaced;
use Illuminate\Support\Facades\Log;

class RecordOrderActivity
{
    public function handle(OrderPlaced $event): void
    {
        Log::info('Order placed successfully.', [
            'order_id' => $event->order->id,
        ]);
    }
}

Controller

app/Http/Controllers/OrderController.php

<?php

namespace App\Http\Controllers;

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

class OrderController extends Controller
{
    public function store(Request $request)
    {
        $validated = $request->validate([
            'customer_name' => 'required|string|max:100',
            'customer_email' => 'required|email',
            'total' => 'required|numeric|min:0',
        ]);

        $order = Order::create($validated);

        OrderPlaced::dispatch($order);

        return response()->json([
            'success' => true,
            'message' => 'Order placed successfully.',
            'order_id' => $order->id,
        ], 201);
    }
}

Route

routes/web.php

use App\Http\Controllers\OrderController;
use Illuminate\Support\Facades\Route;

Route::post('/orders', [
    OrderController::class,
    'store'
]);

When a client sends a POST request to:

/orders

Laravel creates the order and dispatches:

OrderPlaced::dispatch($order);

The registered listeners can then independently perform additional operations.


Events and Broadcasting

Laravel events can also be used with Laravel’s broadcasting system when real-time updates are required.

A broadcastable event can implement:

ShouldBroadcast

For example:

use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class OrderPlaced implements ShouldBroadcast
{
    // ...
}

Broadcasting is useful for features such as:

  • Real-time notifications

  • Chat applications

  • Live dashboards

  • Order status updates

  • Real-time activity feeds

Broadcasting requires additional configuration, such as a supported broadcasting connection and client-side setup.

For normal server-side event handling, you do not need broadcasting.


Best Practices

1. Keep Events Simple

An event should primarily carry information about what happened.

OrderPlaced::dispatch($order);

Avoid putting large amounts of business logic inside the event class.

2. Keep Listeners Focused

A listener should generally have one clear responsibility.

For example:

SendOrderConfirmation

should focus on sending the confirmation rather than also updating analytics and warehouse systems.

3. Queue Slow Operations

Use queued listeners for operations that do not need to complete during the user’s request.

class SendOrderConfirmation implements ShouldQueue
{
    // ...
}

4. Use Descriptive Names

Use names that clearly communicate what happened:

OrderPlaced
PaymentCompleted
UserRegistered

5. Avoid Excessive Events

Events are powerful, but not every method needs an event. Use them when decoupling independent application responsibilities provides a real benefit.

6. Test Event Dispatching

Use:

Event::fake();

OrderPlaced::dispatch($order);

Event::assertDispatched(OrderPlaced::class);

7. Monitor Queued Listeners

Queued listeners can fail or be retried. Configure appropriate retry, timeout, and failed-job handling for production applications.


Summary

Laravel Events provide a clean way to decouple different parts of an application.

The basic process is:

Create Event
     ↓
Create Listener
     ↓
Register / Discover Listener
     ↓
Dispatch Event
     ↓
Listener Executes

Important points to remember:

  • Events represent something that happened in the application.

  • Listeners define what should happen in response.

  • Events are commonly stored in app/Events.

  • Listeners are commonly stored in app/Listeners.

  • Create events with php artisan make:event.

  • Create listeners with php artisan make:listener.

  • Dispatch events with Event::dispatch() or event().

  • One event can have multiple listeners.

  • Laravel can automatically discover event listeners.

  • Use ShouldQueue for listeners that perform slow operations.

  • Use subscribers when one class needs to handle multiple events.

  • Use Event::fake() to test event dispatching.

  • Broadcasting extends events to real-time client communication.

  • Keep events small and listeners focused.

Laravel’s event system is especially valuable in applications where one action can trigger several independent operations. It improves separation of concerns and makes the application easier to maintain, test, and extend.