Decoupling Laravel Logic with Events and Listeners
The Problem of Code Entanglement
When you first build an action, like purchasing a ticket, the code is usually clean. But as your application scales, that single job begins to accumulate unrelated responsibilities. Suddenly, your ProcessTicketPurchase job is saving records, emailing users, alerting admins, and updating marketing analytics. This creates a "telephone game" effect where a single class becomes a discombobulated mess. To keep your codebase maintainable, you must separate the core action from its side effects.
Prerequisites
To follow this guide, you should be comfortable with
Key Libraries & Tools
- Laravel: A robust PHP framework providing the built-in Event Dispatcher.
- Artisan: The command-line interface used to scaffold event and listener classes.
- Livewire: Used in the examples to handle frontend logic and dispatch events.
Code Walkthrough
First, we generate the event class. Think of an event as a simple data container.
// Generate the event
php artisan make:event TicketPurchased
Inside the event, we define the data it needs to carry, such as the User and the ticket type string. By keeping this class logic-free, we ensure it only serves as a messenger.
public function __construct(
public User $user,
public string $type
) {}
Next, we create the listener. This is where the side-effect logic (like sending an email) lives. Using
// Generate the listener
php artisan make:listener SendTicketPurchasedNotification
In the handle method of the listener, we access the data from the event instance to perform the task.
public function handle(TicketPurchased $event): void
{
$event->user->notify(new TicketPurchasedNotification($event->type));
}
Syntax Notes
public variables in the constructor, the framework automatically assigns them. Additionally, if you pass an
Practical Examples
Beyond manual dispatching, you can use Model Events. By defining a $dispatchesEvents property on a model, you can trigger actions automatically whenever a record is created, updated, or deleted. This is perfect for audit logs or clearing caches without cluttering your controllers.
Tips & Gotchas
Always consider if a listener should be queued. If the listener performs an external API call or sends an email, implement the ShouldQueue interface. This prevents your user from waiting for these background tasks to finish before their page reloads. Keep your events focused on what happened, and let listeners decide what to do about it.
