Events & Listeners

As applications grow, a single action often needs to trigger several unrelated reactions. Publishing an article might need to notify subscribers, clear a cache, update a search index, and log an analytics entry — all from one button click. If you write all of that directly inside the controller method, the controller quickly turns into a long, tangled list of unrelated responsibilities, and every new reaction means editing code that already works. Laravel's event system solves this by letting you announce that "something happened" (an event) without the code that announces it needing to know who, if anyone, is listening. Separate listener classes react to that announcement, each one focused on a single job. This page walks through generating events and listeners, dispatching events, registering the mapping between them, and a full worked example — plus why this decouples your code in a way that pays off as the application grows.

What Problem Events Actually Solve

Without events, "publish an article" and "notify subscribers" get welded together inside the same method. That coupling causes two concrete problems:

ProblemWhy it hurts
Unrelated logic piles up in one placeThe ArticleController ends up knowing about notifications, caching, search indexing — none of which are really its job
Adding a new reaction means editing old codeEvery time the business grows a new requirement ("also post to Slack when an article is published"), you have to open and modify a controller that already works and is already tested

Events flip this around: the controller simply says "an article was published" and stops caring what happens next. Anything that needs to react subscribes to that event independently.

Generating an Event and a Listener

Artisan can scaffold both halves of the pair in one step. Generate the event first, then generate a listener wired to that specific event with the --event flag:

$ php artisan make:event ArticlePublished
$ php artisan make:listener SendPublishedNotification --event=ArticlePublished

The first command creates app/Events/ArticlePublished.php. The second creates app/Listeners/SendPublishedNotification.php, already type-hinted to receive an ArticlePublished event in its handle() method, so you don't have to wire that connection up by hand.

An Event Class Is Just a Data Carrier

An event class does almost nothing on its own — it has no logic, no side effects. Its entire job is to hold the data relevant to what just happened, so that any listener can get at it. Typically that means a constructor that accepts the relevant model (or value) and stores it as a public property:

<?php

namespace App\Events;

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

class ArticlePublished
{
    use Dispatchable, SerializesModels;

    public function __construct(public Article $article)
    {
    }
}

That's it — the whole class. Nothing here decides what should happen when an article is published; it just carries the Article instance to whoever ends up listening. The Dispatchable trait adds the static ::dispatch() helper, and SerializesModels keeps the event safe to serialize if a listener is queued.

A Listener Contains the Actual Reaction

All of the real logic — deciding what should actually happen — lives in the listener's handle() method, which receives the event instance and can read its public properties:

<?php

namespace App\Listeners;

use App\Events\ArticlePublished;
use App\Notifications\NewArticleNotification;

class SendPublishedNotification
{
    public function handle(ArticlePublished $event)
    {
        $article = $event->article;

        foreach ($article->author->subscribers as $subscriber) {
            $subscriber->notify(new NewArticleNotification($article));
        }
    }
}

Notice the event class above knows nothing about notifications, subscribers, or anything else in this method — that knowledge lives entirely in the listener, exactly where it belongs.

Registering the Event-Listener Mapping

Laravel needs to know that ArticlePublished should trigger SendPublishedNotification. Depending on your Laravel version this mapping is declared in one of two equivalent ways:

ApproachHow it works
Explicit registration (EventServiceProvider)A $listen array maps each event class to an array of listener classes that should run when it fires — this is the traditional, always-works approach and remains fully valid
Auto-discoveryNewer Laravel versions scan your app/Listeners directory and automatically wire up listeners whose handle() method is type-hinted against an event class, so no manual array entry is required
// app/Providers/EventServiceProvider.php (explicit style)
protected $listen = [
    ArticlePublished::class => [
        SendPublishedNotification::class,
    ],
];

Either way, the effect for the rest of this page is identical: dispatching ArticlePublished will cause SendPublishedNotification::handle() to run.

Dispatching an Event

An event can be dispatched from anywhere in your application code — a controller, a model, a job, another listener. Both of the following are equivalent; the helper function form and the static method form do the same thing:

<?php

event(new ArticlePublished($article));

// or, equivalently, using the Dispatchable trait's helper
ArticlePublished::dispatch($article);

Full Worked Example: Publishing an Article

Putting the pieces together, here is the controller method that publishes an article. Notice how little it knows — it saves the article and announces that it was published; it has no idea that a notification will be sent as a result:

<?php

namespace App\Http\Controllers;

use App\Events\ArticlePublished;
use App\Models\Article;

class ArticleController extends Controller
{
    public function publish(Article $article)
    {
        $article->update(['status' => 'published']);

        ArticlePublished::dispatch($article);

        return redirect()->back();
    }
}

When this method runs, Laravel's event dispatcher looks up every listener registered against ArticlePublished and calls each one's handle() method, passing along the event instance — which is how SendPublishedNotification gets access to $article.

Why This Beats Calling the Notification Directly

It would have been shorter to just call the notification-sending code directly inside publish(). The payoff of the event/listener split shows up the moment a second reaction is needed. Suppose the business later asks for the homepage's "popular articles" cache to also be cleared whenever an article is published. Without events, that means opening ArticleController::publish() — code that already works and is already tested — and adding another line to it. With events, it means writing one new listener class and registering it:

$ php artisan make:listener ClearPopularArticlesCache --event=ArticlePublished

ArticleController::publish() is never touched again. This is a direct, practical instance of the Open/Closed Principle: the system is open to new behavior (a new listener) but closed to modification of existing, working code. Each listener also stays small and focused on exactly one concern, which makes each one easy to test in isolation — testing "does publishing notify subscribers" and "does publishing clear the cache" become two separate, simple tests instead of one tangled one.

Looking Ahead: Queued Listeners

Sending a notification to every subscriber could be slow if there are thousands of them — and right now, that work happens synchronously, meaning the user who clicked "Publish" has to wait for every notification to finish sending before the page responds. A listener class can implement the ShouldQueue interface to tell Laravel "run this in the background instead":

class SendPublishedNotification implements ShouldQueue
{
    // unchanged handle() method — Laravel handles the queueing automatically
}

With that one interface added, Laravel pushes the listener's execution onto a queue instead of running it immediately, and the controller's redirect()->back() response returns right away. The next lesson, Jobs & Queues, covers exactly how that background processing works.

Next up: Continue to Jobs & Queues to see how background work — including queued listeners — actually gets processed.