aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Web Development

Laravel Events and Listeners: Decoupled Code Guide

A Laravel event is the cleanest way to announce that "something happened" in your application while keeping the code that reacts to it completely separate. Instead of cramming three different tasks — sending a welcome email, writing a log entry and updating a statistic — into a single controller when a user signs up, you publish one event and the listeners that subscribe to it share the work. The result is a loosely coupled, testable code base that is open to growth. In this article we will build the event/listener architecture from scratch, queue it, and see how to use it in real scenarios step by step.

What is the event and listener idea?

The idea is simple: an event is a message object that represents "something that happened" — for example OrderShipped. A listener is the code that reacts to that event — such as emailing the customer. The code that publishes the event does not need to know who is listening. This is the classic publish/subscribe pattern, and it brings a few key advantages:

  • Loose coupling: The controller has no idea whether a mail was sent or a log was written. To add a new reaction you simply write a new listener.
  • Single responsibility: Each listener does one job, so classes stay small and readable.
  • Easy testing: You can assert that the event was dispatched in a single line.

Creating events and listeners

You can generate both with artisan. When creating the listener, the --event flag tells it which event to listen for, which saves you some wiring:

php artisan make:event OrderShipped
php artisan make:listener SendShipmentNotification --event=OrderShipped

An event class usually just carries data. Passing the relevant model into the constructor is enough:

<?php

namespace App\Events;

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

class OrderShipped
{
    use Dispatchable, SerializesModels;

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

The listener does the actual work in its handle() method. The parameter you type-hint determines which event it listens for:

<?php

namespace App\Listeners;

use App\Events\OrderShipped;
use App\Notifications\ShipmentNotification;

class SendShipmentNotification
{
    public function handle(OrderShipped $event): void
    {
        $event->order->user->notify(
            new ShipmentNotification($event->order)
        );
    }
}

Registering listeners

In Laravel 11 and 12, listeners are discovered automatically. When a class in the app/Listeners folder type-hints an event in its handle() method, the framework wires it up for you; no extra registration is needed. This removes the need for the $listen array inside the old EventServiceProvider.

There are still cases where manual registration is handy — for example, a quick closure-based listener. You can register one in the boot() method of AppServiceProvider:

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

public function boot(): void
{
    Event::listen(function (OrderShipped $event) {
        // a quick, one-off reaction
    });
}

Dispatching the event

To publish an event you use the static dispatch method or the global event() helper. Both do the same thing:

use App\Events\OrderShipped;

// static method
OrderShipped::dispatch($order);

// or the event() helper
event(new OrderShipped($order));

The moment this line runs, every listener that subscribes to OrderShipped is triggered one by one. Your controller only publishes the event; the rest is up to the listeners. A typical usage looks like this:

public function ship(Order $order)
{
    $order->markAsShipped();

    OrderShipped::dispatch($order);

    return back()->with('status', 'Order shipped.');
}

Queueing listeners

Slow work like sending email should not hold up the request. To move a listener to the background, all you need to do is implement the ShouldQueue interface:

<?php

namespace App\Listeners;

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

class SendShipmentNotification implements ShouldQueue
{
    public int $tries = 3;
    public int $backoff = 30;

    public function handle(OrderShipped $event): void
    {
        // runs in the background
    }
}

The listener is now pushed onto a queue and processed by a worker. $tries sets the number of retries and $backoff the wait (in seconds) between them. For the queue to run you need to keep a worker alive in the background:

php artisan queue:work

You can also add a failed() method that fires when the listener fails permanently, where you might alert an admin or write a log. An important detail: queued listeners serialize the event object, so thanks to the SerializesModels trait only the model's key is stored and the model is reloaded fresh from the database when the job runs.

Grouping listeners with an event subscriber

If you want to collect several listeners for a single topic in one class, you can use a subscriber. A subscriber is a class that registers its own event mappings through a subscribe() method:

<?php

namespace App\Listeners;

use Illuminate\Events\Dispatcher;

class OrderEventSubscriber
{
    public function handleShipped($event): void { /* ... */ }
    public function handleCancelled($event): void { /* ... */ }

    public function subscribe(Dispatcher $events): array
    {
        return [
            \App\Events\OrderShipped::class => 'handleShipped',
            \App\Events\OrderCancelled::class => 'handleCancelled',
        ];
    }
}

This approach is ideal for keeping related events, such as an order lifecycle, in a single readable place.

Frequently Asked Questions

Should I use an event/listener or a job?

The two complement each other. A job says "do this task in the background"; it is a single, specific task. An event says "this happened" and can trigger several independent reactions. If there is just one task, dispatch a job directly; if multiple parties need to react to a single occurrence, use an event/listener (and queue the listener to gain the job's benefit too).

How do I assert that an event was dispatched in a test?

You fake events with Event::fake(), then verify with Event::assertDispatched(OrderShipped::class). This lets you test only that the event fired, without running the real listeners.

How does this relate to model events?

Eloquent automatically fires its own model events such as created, updated and deleted. Listening to them with an observer class is often cleaner for work tied to the model lifecycle (for example generating a slug on creation). For broader, business-level occurrences, writing your own event classes is the better fit.

If you want to make your code loosely coupled, we can split the crowded logic in your existing controllers into events and listeners together; get in touch to streamline your Laravel architecture.

Bu kategorideki tüm yazılar →

Devamı için