Sabuj Kundu 5th Nov 2025


By an experienced Laravel developer — clear explanations, practical examples, and step-by-step flows so you can use events confidently in production.

Why events?

Events decouple pieces of your application. Instead of doing everything inside a controller (or a model), fire an event and let zero, one, or many listeners react. This improves maintainability, testability and enables side-effects like emails, analytics and broadcasts to be handled asynchronously.

High-level flow (route → event → listener)

  1. Route receives a request and points to a controller action.
  2. Controller performs main work (validation, saving), then dispatches an event.
  3. Event Dispatcher (Laravel’s dispatcher) resolves the event and finds registered listeners.
  4. Listeners are resolved from the container and their handle() (or specified method) is executed. If a listener implements ShouldQueue, it is pushed to your queue backend.
  5. Side effects occur in listeners — emails, logging, broadcasts, cache updates, etc.

This flow allows multiple responsibilities to be split into separate classes. The controller doesn’t need to know about emails or analytics — it just dispatches an intent.

Minimal workable example

Use case: When a user registers, we want to send a welcome email and log an analytics event.

1) Route & Controller

// routes/web.php
use App\Http\Controllers\Auth\RegisterController;

Route::post('/register', [RegisterController::class, 'store']);
// app/Http/Controllers/Auth/RegisterController.php
namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Events\UserRegistered;
use App\Models\User;
use Illuminate\Http\Request;

class RegisterController extends Controller
{
    public function store(Request $request)
    {
        $data = $request->validate([
            'name' => 'required|string|max:255',
            'email' => 'required|email|unique:users',
            'password' => 'required|confirmed|min:8',
        ]);

        $user = User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => bcrypt($data['password']),
        ]);

        // Dispatch the event (synchronous by default)
        UserRegistered::dispatch($user);

        return response()->json(['message' => 'Registered'], 201);
    }
}

2) Event

// 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;

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

3) Listeners

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

use App\Events\UserRegistered;
use Illuminate\Contracts\Queue\ShouldQueue; // optional for queued listener
use Illuminate\Queue\InteractsWithQueue;
use Mail;

class SendWelcomeEmail implements ShouldQueue
{
    use InteractsWithQueue;

    public function handle(UserRegistered $event)
    {
        $user = $event->user;
        // Mail::to($user)->send(new WelcomeMail($user));
        // Keep simple for example:
        \Log::info('Send welcome email to: '.$user->email);
    }
}

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

use App\Events\UserRegistered;

class TrackRegistrationAnalytics
{
    public function handle(UserRegistered $event)
    {
        $user = $event->user;
        // push analytics event to external service
        \Log::info('Analytics: new registration for '.$user->id);
    }
}

4) Register listeners (EventServiceProvider)

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

use App\Events\UserRegistered;
use App\Listeners\SendWelcomeEmail;
use App\Listeners\TrackRegistrationAnalytics;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

class EventServiceProvider extends ServiceProvider
{
    protected $listen = [
        UserRegistered::class => [
            SendWelcomeEmail::class,
            TrackRegistrationAnalytics::class,
        ],
    ];

    public function boot()
    {
        parent::boot();
    }
}

Now when UserRegistered::dispatch($user) is called, both listeners are invoked. Because SendWelcomeEmail implements ShouldQueue, it will be pushed to the queue while the analytics listener will run immediately (synchronous).

Step-by-step: what happens internally when you dispatch an event

  1. Dispatch — You call UserRegistered::dispatch($user) or event(new UserRegistered($user)).
  2. Dispatcher — Laravel’s dispatcher receives the event object or name and resolves all listeners mapped to that event from EventServiceProvider::$listen (and any registered subscribers).
  3. Resolve listener — For each listener class, the container constructs an instance. If the listener is queueable (ShouldQueue), Laravel serializes the job and pushes it to the configured queue backend (Redis, database, SQS, etc.).
  4. Handle — Listener’s handle($event) method is invoked. The event object is type-hinted and injected.
  5. Exceptions — If listener throws an exception and it’s queued, the queue worker handles retry/backoff per your queue config. For sync listeners, the exception bubbles up to the dispatcher (and could fail the HTTP request).

The important hook is the container resolving the listener and calling handle() (or whatever method you configured). That resolution gives you constructor injection of services, enabling testable, side-effect free logic.

Queued listeners in detail

If a listener implements ShouldQueue, Laravel serializes the listener (and the event) and queues it. Implement InteractsWithQueue for helper methods (delete(), release()), and remember that closures cannot be serialized, so avoid anonymous functions in queued listeners.

// Example: a heavy job in a queued listener
namespace App\Listeners;

use App\Events\UserRegistered;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;

class ProcessUserOnboarding implements ShouldQueue
{
    use InteractsWithQueue;

    public $tries = 3;        // retry attempts
    public $timeout = 120;    // seconds

    public function handle(UserRegistered $event)
    {
        // heavy tasks: create crm profile, run a data enrichment job, etc.
    }
}

Important: configure your queue worker (e.g. php artisan queue:work) and queue driver in config/queue.php. For local development the sync driver runs jobs synchronously; use database or redis to actually queue them.

Model events vs. custom events

Laravel fires model events automatically: creating, created, updating, updated, deleting, deleted, etc. You can hook into these via model observers or by registering callbacks on the model.

// Registering an observer
// app/Providers/EventServiceProvider.php
protected $observers = [
    \App\Models\User::class => \App\Observers\UserObserver::class,
];

// app/Observers/UserObserver.php
namespace App\Observers;

use App\Models\User;

class UserObserver
{
    public function creating(User $user)
    {
        // before created (e.g. normalize data)
    }

    public function created(User $user)
    {
        // after created (e.g. dispatch other events)
    }
}

Observers are ideal for model-level lifecycle logic; events are better when you want to broadcast domain-specific actions across your app.

Event Subscribers

If you prefer to group listeners into a single class, use an event subscriber:

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

use App\Events\UserRegistered;
use App\Events\UserLoggedIn;

class UserEventSubscriber
{
    public function handleUserRegistered(UserRegistered $event) { /* ... */ }
    public function handleUserLoggedIn(UserLoggedIn $event) { /* ... */ }

    public function subscribe($events)
    {
        $events->listen(
            UserRegistered::class,
            [UserEventSubscriber::class, 'handleUserRegistered']
        );

        $events->listen(
            UserLoggedIn::class,
            [UserEventSubscriber::class, 'handleUserLoggedIn']
        );
    }
}

// Register subscriber in EventServiceProvider::subscribe
protected $subscribe = [
    \App\Listeners\UserEventSubscriber::class,
];

Broadcasting events

To push events to client-side (WebSockets), implement ShouldBroadcast on your event and provide a broadcastOn() channel. Laravel Echo or a WebSocket client can then listen for the event.

// app/Events/OrderShipped.php
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class OrderShipped implements ShouldBroadcast
{
    public $order;

    public function __construct(Order $order) { $this->order = $order; }

    public function broadcastOn()
    {
        return new PrivateChannel('orders.'.$this->order->id);
    }
}

Broadcasting requires a broadcast driver (Pusher, Redis + Laravel WebSockets, etc.) and frontend code (Laravel Echo) to actually receive events.

Testing events and listeners

Laravel’s Event facade provides helpers to fake or assert events:

// In tests
use Illuminate\Support\Facades\Event;
use App\Events\UserRegistered;

public function test_registration_dispatches_event()
{
    Event::fake();

    $this->postJson('/register', [
        'name'=>'Jane', 'email'=>'jane@example.test', 'password'=>'secret', 'password_confirmation'=>'secret'
    ])->assertStatus(201);

    Event::assertDispatched(UserRegistered::class);
}

You can also assert listeners were queued via Queue::fake() or assert that specific listeners handled an event using callback assertions.

Common pitfalls & best practices

  • Don’t overload a listener. Keep listeners focused and small. If a listener grows large, split it into multiple listeners or queue jobs.
  • Avoid business logic in controllers. Use events to delegate post-action tasks.
  • Be careful with queued listeners and serialization. Avoid unsafely serializable objects (like DB connections, closures). Use IDs and re-fetch models in the listener if needed.
  • Use SerializesModels on events to automatically rehydrate Eloquent models when queued.
  • Use observers for model lifecycle logic, events for domain actions. Observers = model concerns; events = application/domain concerns.

Advanced patterns & real world use cases

1) Use-case: audit logs

Fire a ModelChanged event from a repository; subscribe an AuditListener that writes structured audit logs to a separate table or external service.

2) Use-case: decoupled integrations

After a purchase, dispatch OrderPlaced. One listener charges payment, another updates inventory, another notifies CRM. Each behavior can run independently and be retried separately if queued.

3) Use-case: async image processing

Upload an image, dispatch ImageUploaded. A queued listener pushes a job to resize/optimize and store derivatives, keeping HTTP response fast.

Quick reference / cheatsheet

// Dispatching events
Event::dispatch(new SomeEvent($payload));
SomeEvent::dispatch($payload); // when using Dispatchable

// Listener that's queued
class DoWork implements ShouldQueue { public function handle(SomeEvent $e) { /*...*/ } }

// Register
// EventServiceProvider::$listen or $subscribe or via $events->listen in boot()

// Testing
Event::fake(); Event::assertDispatched(SomeEvent::class);

Summary

Laravel’s event system is simple but powerful: dispatch an event, map listeners (or subscribers), and let the container resolve and run them. Use events to keep controllers thin, make your app modular, and scale heavy tasks with queues. When building, keep listeners single-purpose, test interactions with Event::fake(), and be mindful of serialization when queuing.

Need to build a Website or Application?

Since 2011, Codeboxr has been transforming client visions into powerful, user-friendly web experiences. We specialize in building bespoke web applications that drive growth and engagement.

Our deep expertise in modern technologies like Laravel and Flutter allows us to create robust, scalable solutions from the ground up. As WordPress veterans, we also excel at crafting high-performance websites and developing advanced custom plugins that extend functionality perfectly to your needs.

Let’s build the advanced web solution your business demands.