Role-based access control patterns in Laravel
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):
$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):
$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
// 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