0

Role-based access control patterns in Laravel

Intermediate5 min read·lv-17-006
interviewsql

Concept

Events in Laravel are domain signals — objects that announce something happened. Listeners react to events. This decouples the code that triggers an action from the code that responds to it.

Without events (tightly coupled):

php
$user = User::create($data);
Mail::to($user)->send(new WelcomeEmail($user));
$user->subscribe('general');
Log::info('User registered: ' . $user->id);

The UserController::store() now knows about emails, subscriptions, and logging. Every new reaction requires modifying the controller.

With events (decoupled):

php
$user = User::create($data);
event(new UserRegistered($user));

UserRegistered is dispatched. Listeners react independently. Adding a new reaction (e.g., HubSpot sync) = add a new listener, never touch the controller.

Event = data carrier: Events are plain PHP objects (no business logic). They carry the data listeners need: the User, the Order, timestamps.

When NOT to use events: For simple sequential steps in one request where the caller needs the result synchronously. Events become overhead without benefit in simple CRUD operations.

Event service provider: App\Providers\EventServiceProvider maps events to listeners. Laravel 10+ auto-discovers listeners following the handle(EventClass $event) signature.

event() helper: Dispatches synchronously. Event::dispatch($event) is equivalent.

Code Example

php
<?php
// Defining an event — data carrier
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 readonly Order $order,
        public readonly string $paymentMethod,
    ) {}
}

// Dispatching
event(new OrderPlaced($order, 'stripe'));
OrderPlaced::dispatch($order, 'stripe'); // static helper via Dispatchable trait

// Listeners
namespace App\Listeners;

use App\Events\OrderPlaced;

class SendOrderConfirmationEmail
{
    public function handle(OrderPlaced $event): void
    {
        \Illuminate\Support\Facades\Mail::to($event->order->customer)
            ->send(new \App\Mail\OrderConfirmation($event->order));
    }
}

class NotifyWarehouse
{
    public function handle(OrderPlaced $event): void
    {
        // Notify warehouse system
        app(\App\Services\WarehouseService::class)->notifyNewOrder($event->order->id);
    }
}

class UpdateInventory
{
    public function handle(OrderPlaced $event): void
    {
        foreach ($event->order->items as $item) {
            $item->product->decrement('stock', $item->quantity);
        }
    }
}

// EventServiceProvider — register event → listener mapping
protected $listen = [
    \App\Events\OrderPlaced::class => [
        \App\Listeners\SendOrderConfirmationEmail::class,
        \App\Listeners\NotifyWarehouse::class,
        \App\Listeners\UpdateInventory::class,
    ],
];
// All three listeners fire when OrderPlaced is dispatched