Laravel Events & Listeners Explained - Complete Guide With Real Example

๐Ÿ‘๏ธ 7 Views
|
๐Ÿ“… Sep 01, 2026
|
โฑ๏ธ 15 min read
Laravel Events & Listeners Explained - Complete Guide With Real Example
Laravel: Up & Running
โœฆ Developer Pick

Laravel: Up & Running

What sets Laravel apart from other PHP web frameworks? Speed and simplicity, for starters. This rapid application development framework and its ecosystem of tools let you quickly build new sites and applications with clean, readable code.

As your Laravel application grows, you will start to notice a pattern โ€” a single action triggers multiple side effects. A user registers and you need to send a welcome email, create a default profile, notify the admin, and log the activity. If you put all of that logic inside your controller or model, it becomes a mess very quickly. Laravel Events and Listeners exist to solve exactly this problem โ€” keeping your code clean, decoupled, and easy to extend without touching existing code.

In this guide we will cover how Laravel's event system works, how to create events and listeners, how to queue listeners for performance, and walk through a complete real-world example from start to finish.

What Are Events and Listeners?

Events and Listeners implement the Observer pattern in Laravel. An Event is a class that represents something that happened in your application โ€” a user registered, an order was placed, a payment failed. A Listener is a class that responds to that event and handles a specific piece of logic โ€” send an email, update a record, notify a third-party service.

The key benefit is decoupling. The part of your code that fires the event does not need to know anything about what happens as a result. It just says "this happened" and walks away. The listeners take care of the rest. This means you can add new behaviour to an event by creating a new listener โ€” without touching the original code that fires the event.

// Without events โ€” tightly coupled, hard to maintain
public function register(Request $request)
{
    $user = User::create($request->validated());

    // All of this lives in the controller โ€” grows forever
    Mail::to($user)->send(new WelcomeEmail($user));
    Profile::create(['user_id' => $user->id]);
    AdminNotification::send($user);
    ActivityLog::record('user_registered', $user->id);
    Slack::notify("New user: {$user->name}");

    return redirect('/dashboard');
}

// With events โ€” clean, decoupled, extensible
public function register(Request $request)
{
    $user = User::create($request->validated());

    event(new UserRegistered($user)); // fire and forget

    return redirect('/dashboard');
}

The Real-World Example We Will Build

We will build the event system for a blog application. When a user publishes a post, we want to:

  • Send a notification email to all subscribers
  • Update the user's post count in their profile
  • Log the publish activity
  • Send a ping to search engines (optional)

All of this happens when one event fires โ€” PostPublished. We will build the whole thing step by step.

Step 1 โ€” Register Events and Listeners in EventServiceProvider

The first step is to tell Laravel which listeners should respond to which events. This is done in app/Providers/EventServiceProvider.php:

// app/Providers/EventServiceProvider.php
namespace App\Providers;

use App\Events\UserRegistered;
use App\Events\PostPublished;
use App\Listeners\SendWelcomeEmail;
use App\Listeners\CreateUserProfile;
use App\Listeners\NotifySubscribers;
use App\Listeners\UpdatePostCount;
use App\Listeners\LogPostActivity;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

class EventServiceProvider extends ServiceProvider
{
    protected $listen = [
        // UserRegistered event โ†’ multiple listeners
        UserRegistered::class => [
            SendWelcomeEmail::class,
            CreateUserProfile::class,
        ],

        // PostPublished event โ†’ multiple listeners
        PostPublished::class => [
            NotifySubscribers::class,  // queued โ€” sends emails
            UpdatePostCount::class,    // fast โ€” updates a counter
            LogPostActivity::class,    // fast โ€” writes to log
        ],
    ];
}

Step 2 โ€” Generate Event and Listener Files

Once you have registered your events and listeners, Laravel can generate all the boilerplate files with a single command:

// Generate all registered events and listeners at once
php artisan event:generate

// Or generate individually
php artisan make:event PostPublished
php artisan make:listener NotifySubscribers --event=PostPublished
php artisan make:listener UpdatePostCount --event=PostPublished
php artisan make:listener LogPostActivity --event=PostPublished

Step 3 โ€” Create the Event Class

An event class is a simple data container โ€” it holds the data that listeners will need. For our PostPublished event, the listeners need access to the post that was published:

// app/Events/PostPublished.php
namespace App\Events;

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

class PostPublished
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    // The post that was published โ€” available to all listeners
    public Post $post;

    public function __construct(Post $post)
    {
        $this->post = $post;
    }
}
// app/Events/UserRegistered.php
namespace App\Events;

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

class UserRegistered
{
    use Dispatchable, SerializesModels;

    public User $user;

    public function __construct(User $user)
    {
        $this->user = $user;
    }
}

Tip: The SerializesModels trait is important when listeners are queued. It serializes Eloquent models properly so they can be stored in the queue and re-fetched from the database when the queued listener runs โ€” rather than trying to serialize the entire model object.

Step 4 โ€” Create the Listeners

Each listener has a handle() method that receives the event and does one specific thing. Keep each listener focused on a single responsibility:

Listener 1 โ€” Notify Subscribers (Queued)

// app/Listeners/NotifySubscribers.php
namespace App\Listeners;

use App\Events\PostPublished;
use App\Mail\NewPostNotification;
use App\Models\Subscriber;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Mail;

class NotifySubscribers implements ShouldQueue
{
    use InteractsWithQueue;

    // Queue configuration
    public string $queue   = 'notifications';
    public int    $delay   = 0;
    public int    $tries   = 3; // retry 3 times on failure

    public function handle(PostPublished $event): void
    {
        $post = $event->post;

        // Get all active subscribers
        $subscribers = Subscriber::where('is_active', true)->get();

        foreach ($subscribers as $subscriber) {
            Mail::to($subscriber->email)
                ->send(new NewPostNotification($post, $subscriber));
        }
    }

    // Called if all retries fail
    public function failed(PostPublished $event, \Throwable $exception): void
    {
        \Log::error("Failed to notify subscribers for post: {$event->post->id}", [
            'error' => $exception->getMessage()
        ]);
    }
}

Listener 2 โ€” Update Post Count

// app/Listeners/UpdatePostCount.php
namespace App\Listeners;

use App\Events\PostPublished;

class UpdatePostCount
{
    // No ShouldQueue โ€” this is fast, run synchronously
    public function handle(PostPublished $event): void
    {
        $post = $event->post;

        // Increment the author's published post count
        $post->user()->increment('published_posts_count');
    }
}

Listener 3 โ€” Log Post Activity

// app/Listeners/LogPostActivity.php
namespace App\Listeners;

use App\Events\PostPublished;
use App\Models\ActivityLog;

class LogPostActivity
{
    public function handle(PostPublished $event): void
    {
        $post = $event->post;

        ActivityLog::create([
            'user_id'     => $post->user_id,
            'action'      => 'post_published',
            'description' => "Published post: {$post->title}",
            'meta'        => json_encode([
                'post_id'   => $post->id,
                'post_slug' => $post->slug,
            ]),
        ]);
    }
}

Listener 4 โ€” Send Welcome Email

// app/Listeners/SendWelcomeEmail.php
namespace App\Listeners;

use App\Events\UserRegistered;
use App\Mail\WelcomeEmail;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Mail;

class SendWelcomeEmail implements ShouldQueue
{
    public string $queue = 'emails';

    public function handle(UserRegistered $event): void
    {
        Mail::to($event->user->email)
            ->send(new WelcomeEmail($event->user));
    }
}

Step 5 โ€” Fire the Event

Now that everything is wired up, firing the event is a single line anywhere in your application:

// In your PostController
namespace App\Http\Controllers;

use App\Events\PostPublished;
use App\Models\Post;
use Illuminate\Http\Request;

class PostController extends Controller
{
    public function publish(Request $request, Post $post)
    {
        // Authorization check
        $this->authorize('publish', $post);

        // Update the post status
        $post->update([
            'published_at' => now(),
            'is_published' => true,
        ]);

        // Fire the event โ€” all listeners handle the rest
        event(new PostPublished($post));

        // Or using the static dispatch method (same thing)
        // PostPublished::dispatch($post);

        return redirect()
            ->route('posts.show', $post->slug)
            ->with('success', 'Your post has been published!');
    }
}
// You can also fire events from models using $dispatchesEvents
// app/Models/Post.php
class Post extends Model
{
    // Automatically fire events when model actions happen
    protected $dispatchesEvents = [
        'created' => PostCreated::class,
        'updated' => PostUpdated::class,
        'deleted' => PostDeleted::class,
    ];
}

// Now whenever a Post is saved โ€” the event fires automatically
// No need to call event() manually in your controller

Step 6 โ€” Queued Listeners

Any listener that implements ShouldQueue will be pushed to your queue instead of running synchronously. This is essential for anything slow โ€” sending emails, calling external APIs, heavy database operations. The HTTP response returns immediately and the work happens in the background.

// Make sure your queue worker is running
php artisan queue:work

// Or for production with Supervisor (recommended)
php artisan queue:work --queue=notifications,emails,default --tries=3

// Check failed jobs
php artisan queue:failed

// Retry failed jobs
php artisan queue:retry all

// Your .env queue configuration
QUEUE_CONNECTION=database  // or redis (recommended for production)

// Create the jobs table if using database driver
php artisan queue:table
php artisan migrate

Event Subscribers โ€” Group Related Listeners in One Class

If you have many listeners that all relate to the same domain โ€” like all post-related events โ€” you can group them into a single subscriber class instead of creating a separate file for each listener:

// app/Listeners/PostEventSubscriber.php
namespace App\Listeners;

use App\Events\PostPublished;
use App\Events\PostDeleted;
use App\Events\PostViewed;
use Illuminate\Events\Dispatcher;

class PostEventSubscriber
{
    // Handle PostPublished event
    public function handlePostPublished(PostPublished $event): void
    {
        \Log::info("Post published: {$event->post->title}");
        $event->post->user()->increment('published_posts_count');
    }

    // Handle PostDeleted event
    public function handlePostDeleted(PostDeleted $event): void
    {
        \Log::info("Post deleted: {$event->post->id}");
        $event->post->user()->decrement('published_posts_count');
    }

    // Handle PostViewed event
    public function handlePostViewed(PostViewed $event): void
    {
        $event->post->increment('view_count');
    }

    // Register all listeners in this subscriber
    public function subscribe(Dispatcher $events): void
    {
        $events->listen(PostPublished::class, [self::class, 'handlePostPublished']);
        $events->listen(PostDeleted::class,   [self::class, 'handlePostDeleted']);
        $events->listen(PostViewed::class,    [self::class, 'handlePostViewed']);
    }
}

// Register the subscriber in EventServiceProvider
protected $subscribe = [
    PostEventSubscriber::class,
];

Model Events โ€” Built-in Eloquent Events

Eloquent models fire their own events automatically during the lifecycle of a record. You can hook into these without creating custom event classes:

// Available model events:
// creating, created, updating, updated, saving, saved
// deleting, deleted, restoring, restored, retrieved

// Method 1: observing in the model using boot()
class Post extends Model
{
    protected static function boot(): void
    {
        parent::boot();

        // Before creating โ€” generate slug automatically
        static::creating(function (Post $post) {
            $post->slug = Str::slug($post->title);
        });

        // After creating
        static::created(function (Post $post) {
            \Log::info("New post created: {$post->id}");
        });

        // Before deleting โ€” clean up related data
        static::deleting(function (Post $post) {
            $post->comments()->delete();
            $post->tags()->detach();
        });
    }
}

// Method 2: Observer class (cleaner for many hooks)
php artisan make:observer PostObserver --model=Post

// app/Observers/PostObserver.php
class PostObserver
{
    public function creating(Post $post): void
    {
        $post->slug = Str::slug($post->title);
    }

    public function created(Post $post): void
    {
        ActivityLog::record('post_created', $post->id);
    }

    public function updated(Post $post): void
    {
        if ($post->wasChanged('is_published') && $post->is_published) {
            PostPublished::dispatch($post); // fire custom event
        }
    }

    public function deleted(Post $post): void
    {
        $post->comments()->delete();
        $post->tags()->detach();
    }
}

// Register the observer in AppServiceProvider or a model
// In AppServiceProvider::boot()
Post::observe(PostObserver::class);

Testing Events and Listeners

// Laravel makes it easy to test events without actually firing listeners
use App\Events\PostPublished;
use App\Listeners\NotifySubscribers;
use Illuminate\Support\Facades\Event;

// Test that an event was fired
public function test_post_published_event_is_fired()
{
    Event::fake();

    $post = Post::factory()->create();

    $this->actingAs($post->user)
         ->post("/posts/{$post->id}/publish");

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

// Test that an event was NOT fired
Event::assertNotDispatched(PostPublished::class);

// Test a specific listener handles the event correctly
public function test_notify_subscribers_listener()
{
    $post       = Post::factory()->published()->create();
    $subscriber = Subscriber::factory()->create();

    Mail::fake();

    $listener = new NotifySubscribers();
    $listener->handle(new PostPublished($post));

    Mail::assertSent(NewPostNotification::class, function ($mail) use ($subscriber) {
        return $mail->hasTo($subscriber->email);
    });
}

Events vs Jobs โ€” When to Use Which

Events + Listeners Jobs
Best for One action triggers multiple things One specific task dispatched to queue
Listeners Multiple per event One handler
Coupling Loosely coupled โ€” easy to extend Directly dispatched
Queue support Yes โ€” implement ShouldQueue Yes โ€” built in
Example use case User registered โ†’ email + profile + log Generate a PDF report

Common Mistakes to Avoid

1. Not queuing slow listeners

// โŒ Sending email synchronously โ€” slows down the HTTP response
class SendWelcomeEmail
{
    public function handle(UserRegistered $event): void
    {
        Mail::to($event->user)->send(new WelcomeEmail($event->user)); // blocks
    }
}

// โœ… Queue the listener โ€” response returns immediately
class SendWelcomeEmail implements ShouldQueue
{
    public function handle(UserRegistered $event): void
    {
        Mail::to($event->user)->send(new WelcomeEmail($event->user));
    }
}

2. Putting too much logic in the event class

// โŒ Event classes should be data containers only
class PostPublished
{
    public function __construct(public Post $post)
    {
        // Don't put business logic here
        $this->post->update(['published_at' => now()]); // โŒ wrong place
    }
}

// โœ… Event just holds the data
class PostPublished
{
    public function __construct(public Post $post) {}
}

3. Forgetting to register events in EventServiceProvider

// If you create event and listener files manually
// without running event:generate or registering in $listen
// the listener will never fire โ€” no error thrown, just silent failure

// Always verify your EventServiceProvider $listen array
// Or use event:list to see all registered events
php artisan event:list

Final Thought

Laravel Events and Listeners are one of the most elegant patterns in the framework. Once you start using them, you will find yourself reaching for them naturally whenever an action in your application needs to trigger multiple downstream effects. They keep your controllers thin, your models focused, and your codebase easy to extend without touching what already works.

The real power shows when your application grows. Need to add a new behaviour when a post is published? Add a new listener. No changes to the controller, no changes to existing listeners, no risk of breaking what already works. Just a new class that handles one thing.

If you are building a Laravel blog application or any data-driven app, also check out our guide on Laravel Eloquent Relationships Explained with Real-World Examples - understanding relationships is the foundation of building the kind of data models that make events and listeners most useful.

Have a specific use case you are trying to implement with events in your Laravel project? Drop a message on our contact page and we will help you figure out the right approach.

Laravel: Up & Running
โœฆ Developer Pick

Laravel: Up & Running

What sets Laravel apart from other PHP web frameworks? Speed and simplicity, for starters. This rapid application development framework and its ecosystem of tools let you quickly build new sites and applications with clean, readable code.

Subscribe to Our Newsletter

Join Our Developer Community!

Unsubscribe Anytime | No Spam