Laravel – Event Handling
Laravel Events provide a simple way to implement the observer pattern in your application. Events allow different parts of an application to communicate without becoming tightly coupled.
For example, when a customer places an order, your application might need to:
-
Send a confirmation email
-
Record an activity
-
Update analytics
-
Send a notification
-
Process a background job
Instead of placing all these operations inside the order controller, you can dispatch an event and allow independent listeners to handle each task.
A typical event workflow looks like this:
Application Action
↓
Dispatch Event
↓
Laravel Event System
↓
┌──────────────┬──────────────┬──────────────┐
↓ ↓ ↓
Listener 1 Listener 2 Listener 3
↓ ↓ ↓
Email Activity Notification
This makes applications easier to maintain and extend.
Events and Listeners
Laravel uses two main classes:
Event
An event represents something that happened.
Examples:
OrderPlaced
UserRegistered
PaymentCompleted
InvoiceCreated
Listener
A listener defines what should happen when an event occurs.
For example:
OrderPlaced
↓
SendOrderConfirmation
One event can have multiple listeners.
OrderPlaced
├── SendOrderConfirmation
├── RecordOrderActivity
└── NotifyWarehouse
Each listener can perform its own task independently.
Event and Listener Locations
By convention, events are stored in:
app/Events
Listeners are stored in:
app/Listeners
For example:
app/
├── Events/
│ └── OrderPlaced.php
│
└── Listeners/
├── SendOrderConfirmation.php
└── RecordOrderActivity.php
Creating an Event
You can create an event using Artisan:
php artisan make:event OrderPlaced
Laravel creates:
app/Events/OrderPlaced.php
A modern event class can look like this:
<?php
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 Order $order
) {
}
}
The event receives an Order model through its constructor.
This allows listeners to access the order that caused the event.
Understanding Dispatchable
The Dispatchable trait allows the event to be dispatched conveniently.
For example:
OrderPlaced::dispatch($order);
Instead of:
event(new OrderPlaced($order));
Both approaches can be used.
Creating a Listener
Create a listener using Artisan:
php artisan make:listener SendOrderConfirmation --event=OrderPlaced
Laravel creates:
app/Listeners/SendOrderConfirmation.php
A listener can look like this:
<?php
namespace App\Listeners;
use App\Events\OrderPlaced;
class SendOrderConfirmation
{
public function __construct()
{
//
}
public function handle(OrderPlaced $event): void
{
$order = $event->order;
// Send confirmation email...
}
}
The handle() method receives the event instance.
Because the event contains the order:
$event->order
the listener can access all the order information.
Registering Event Listeners
Laravel applications can discover event/listener relationships automatically when event discovery is enabled.
You can also explicitly register event/listener mappings using the application’s event configuration.
For applications that use explicit registration, the mapping can be defined through an event service provider or the application’s current event configuration mechanism.
A typical mapping looks like:
protected $listen = [
OrderPlaced::class => [
SendOrderConfirmation::class,
],
];
If one event has multiple listeners:
protected $listen = [
OrderPlaced::class => [
SendOrderConfirmation::class,
RecordOrderActivity::class,
NotifyWarehouse::class,
],
];
This means all three listeners respond to the same event.
The exact registration location depends on the Laravel application structure and version. Modern Laravel applications may use automatic event discovery rather than requiring manual registration.
Event Discovery
Laravel can automatically discover listeners by inspecting listener classes.
For example:
public function handle(OrderPlaced $event): void
{
// Handle event
}
Laravel can determine that SendOrderConfirmation handles OrderPlaced.
You can inspect discovered events and listeners with:
php artisan event:list
This command is useful for verifying which listeners Laravel knows about.
Dispatching an Event
Once the event and listener have been created, you can dispatch the event from a controller, service, job, or other application class.
For example:
use App\Events\OrderPlaced;
OrderPlaced::dispatch($order);
Or:
event(new OrderPlaced($order));
When Laravel dispatches the event, registered listeners are executed.
Complete Event Example
Let’s create a complete order-processing example.
Step 1: Create the Event
Run:
php artisan make:event OrderPlaced
app/Events/OrderPlaced.php
<?php
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 Order $order
) {
}
}
Step 2: Create a Listener
Run:
php artisan make:listener SendOrderConfirmation --event=OrderPlaced
app/Listeners/SendOrderConfirmation.php
<?php
namespace App\Listeners;
use App\Events\OrderPlaced;
use App\Mail\OrderConfirmationMail;
use Illuminate\Support\Facades\Mail;
class SendOrderConfirmation
{
public function handle(OrderPlaced $event): void
{
$order = $event->order;
Mail::to($order->customer_email)
->send(
new OrderConfirmationMail($order)
);
}
}
Now the listener sends a confirmation email whenever an OrderPlaced event occurs.
Step 3: Dispatch the Event
Suppose an order is created in a controller:
<?php
namespace App\Http\Controllers;
use App\Events\OrderPlaced;
use App\Models\Order;
use Illuminate\Http\Request;
class OrderController extends Controller
{
public function store(Request $request)
{
$order = Order::create([
'customer_name' => $request->customer_name,
'customer_email' => $request->customer_email,
'total' => $request->total,
]);
OrderPlaced::dispatch($order);
return response()->json([
'message' => 'Order placed successfully.',
'order_id' => $order->id,
]);
}
}
The controller only needs to create the order and dispatch the event.
The email logic is handled separately by the listener.
Multiple Listeners for One Event
One of the biggest advantages of Laravel events is that one event can have multiple listeners.
Suppose we have:
OrderPlaced
We could create:
SendOrderConfirmation
RecordOrderActivity
NotifyWarehouse
UpdateSalesAnalytics
Each listener can perform a separate task.
For example:
RecordOrderActivity
<?php
namespace App\Listeners;
use App\Events\OrderPlaced;
use Illuminate\Support\Facades\Log;
class RecordOrderActivity
{
public function handle(OrderPlaced $event): void
{
Log::info('New order placed.', [
'order_id' => $event->order->id,
]);
}
}
Event Mapping
protected $listen = [
OrderPlaced::class => [
SendOrderConfirmation::class,
RecordOrderActivity::class,
],
];
Now both listeners respond when the event is dispatched.
Queued Event Listeners
Some event tasks can take significant time.
For example:
-
Sending emails
-
Generating reports
-
Processing images
-
Calling external APIs
-
Sending notifications
These operations can be moved to a queue.
A listener can implement ShouldQueue:
<?php
namespace App\Listeners;
use App\Events\OrderPlaced;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendOrderConfirmation implements ShouldQueue
{
public function handle(OrderPlaced $event): void
{
// Long-running operation
}
}
When Laravel processes the event, the listener can be handled through the configured queue system.
Start the queue worker with:
php artisan queue:work
This prevents slow operations from unnecessarily delaying the user’s request.
Queued Listener Configuration
Queued listeners can also define useful properties.
For example:
public $tries = 3;
This allows the job to be attempted multiple times.
A listener can also define a timeout:
public $timeout = 120;
For example:
class SendOrderConfirmation implements ShouldQueue
{
public $tries = 3;
public $timeout = 120;
public function handle(OrderPlaced $event): void
{
// Process event
}
}
Queue settings should be selected according to the operation being performed.
Event Subscribers
Laravel also supports event subscribers.
An event subscriber is a class that can subscribe to multiple events.
For example:
<?php
namespace App\Listeners;
class UserEventSubscriber
{
public function handleUserRegistered($event)
{
// Handle registration
}
public function handleUserLoggedIn($event)
{
// Handle login
}
public function subscribe($events): void
{
$events->listen(
'App\Events\UserRegistered',
[self::class, 'handleUserRegistered']
);
$events->listen(
'Illuminate\Auth\Events\Login',
[self::class, 'handleUserLoggedIn']
);
}
}
Subscribers are useful when several related events are handled by the same class.
Closures as Event Listeners
For small applications or simple events, you can listen for an event using a closure.
For example:
use App\Events\OrderPlaced;
use Illuminate\Support\Facades\Event;
Event::listen(
OrderPlaced::class,
function (OrderPlaced $event) {
logger()->info(
'Order placed: ' . $event->order->id
);
}
);
Closures can be useful for simple application-level event handling.
For larger operations, dedicated listener classes are generally easier to test and maintain.
Conditional Event Handling
A listener can contain application logic to determine whether an action should be performed.
public function handle(OrderPlaced $event): void
{
if ($event->order->total < 100) {
return;
}
// Process high-value order
}
This allows the listener to respond only when certain conditions are met.
Event Listener Dependency Injection
Listeners are resolved through Laravel’s service container.
This means dependencies can be injected into the listener’s constructor.
For example:
class SendOrderConfirmation
{
public function __construct(
protected OrderService $orderService
) {
}
public function handle(OrderPlaced $event): void
{
$this->orderService->process(
$event->order
);
}
}
Laravel automatically resolves the dependency when creating the listener.
Event Naming
Events should generally describe something that has happened.
Good examples:
OrderPlaced
UserRegistered
PaymentCompleted
InvoiceGenerated
PasswordChanged
Avoid names that describe commands when the class represents an event.
For example:
SendOrderEmail
is better represented as a listener action, while:
OrderPlaced
is an event.
A useful pattern is:
Event = What happened
Listener = What should happen next
Event Testing
Events can be tested using Laravel’s event faking functionality.
For example:
use App\Events\OrderPlaced;
use Illuminate\Support\Facades\Event;
Event::fake();
OrderPlaced::dispatch($order);
Event::assertDispatched(OrderPlaced::class);
You can also verify that the event was dispatched with the expected data:
Event::assertDispatched(
OrderPlaced::class,
function ($event) use ($order) {
return $event->order->id === $order->id;
}
);
This allows event behavior to be tested without executing the actual listeners.
Event Discovery Commands
Laravel provides useful Artisan commands for working with events.
List registered events and listeners:
php artisan event:list
Depending on the application and Laravel version, event-related Artisan commands may also include commands for caching or clearing discovered event mappings.
Use:
php artisan event:list --help
to see the commands and options available in your installed Laravel version.
Event Handling vs Direct Method Calls
Without events, a controller might contain:
$order = Order::create($data);
Mail::to($order->customer_email)
->send(new OrderConfirmationMail($order));
Log::info('Order created.');
NotifyWarehouse::send($order);
This creates strong coupling between the order creation process and every additional operation.
With events:
$order = Order::create($data);
OrderPlaced::dispatch($order);
The event system handles the additional work:
OrderPlaced
│
├── SendOrderConfirmation
├── RecordOrderActivity
└── NotifyWarehouse
This separation makes the application easier to extend.
Complete Example
The following example demonstrates an event, listener, controller, and route.
Event
app/Events/OrderPlaced.php
<?php
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 Order $order
) {
}
}
Listener
app/Listeners/RecordOrderActivity.php
<?php
namespace App\Listeners;
use App\Events\OrderPlaced;
use Illuminate\Support\Facades\Log;
class RecordOrderActivity
{
public function handle(OrderPlaced $event): void
{
Log::info('Order placed successfully.', [
'order_id' => $event->order->id,
]);
}
}
Controller
app/Http/Controllers/OrderController.php
<?php
namespace App\Http\Controllers;
use App\Events\OrderPlaced;
use App\Models\Order;
use Illuminate\Http\Request;
class OrderController extends Controller
{
public function store(Request $request)
{
$validated = $request->validate([
'customer_name' => 'required|string|max:100',
'customer_email' => 'required|email',
'total' => 'required|numeric|min:0',
]);
$order = Order::create($validated);
OrderPlaced::dispatch($order);
return response()->json([
'success' => true,
'message' => 'Order placed successfully.',
'order_id' => $order->id,
], 201);
}
}
Route
routes/web.php
use App\Http\Controllers\OrderController;
use Illuminate\Support\Facades\Route;
Route::post('/orders', [
OrderController::class,
'store'
]);
When a client sends a POST request to:
/orders
Laravel creates the order and dispatches:
OrderPlaced::dispatch($order);
The registered listeners can then independently perform additional operations.
Events and Broadcasting
Laravel events can also be used with Laravel’s broadcasting system when real-time updates are required.
A broadcastable event can implement:
ShouldBroadcast
For example:
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
class OrderPlaced implements ShouldBroadcast
{
// ...
}
Broadcasting is useful for features such as:
-
Real-time notifications
-
Chat applications
-
Live dashboards
-
Order status updates
-
Real-time activity feeds
Broadcasting requires additional configuration, such as a supported broadcasting connection and client-side setup.
For normal server-side event handling, you do not need broadcasting.
Best Practices
1. Keep Events Simple
An event should primarily carry information about what happened.
OrderPlaced::dispatch($order);
Avoid putting large amounts of business logic inside the event class.
2. Keep Listeners Focused
A listener should generally have one clear responsibility.
For example:
SendOrderConfirmation
should focus on sending the confirmation rather than also updating analytics and warehouse systems.
3. Queue Slow Operations
Use queued listeners for operations that do not need to complete during the user’s request.
class SendOrderConfirmation implements ShouldQueue
{
// ...
}
4. Use Descriptive Names
Use names that clearly communicate what happened:
OrderPlaced
PaymentCompleted
UserRegistered
5. Avoid Excessive Events
Events are powerful, but not every method needs an event. Use them when decoupling independent application responsibilities provides a real benefit.
6. Test Event Dispatching
Use:
Event::fake();
OrderPlaced::dispatch($order);
Event::assertDispatched(OrderPlaced::class);
7. Monitor Queued Listeners
Queued listeners can fail or be retried. Configure appropriate retry, timeout, and failed-job handling for production applications.
Summary
Laravel Events provide a clean way to decouple different parts of an application.
The basic process is:
Create Event
↓
Create Listener
↓
Register / Discover Listener
↓
Dispatch Event
↓
Listener Executes
Important points to remember:
-
Events represent something that happened in the application.
-
Listeners define what should happen in response.
-
Events are commonly stored in
app/Events. -
Listeners are commonly stored in
app/Listeners. -
Create events with
php artisan make:event. -
Create listeners with
php artisan make:listener. -
Dispatch events with
Event::dispatch()orevent(). -
One event can have multiple listeners.
-
Laravel can automatically discover event listeners.
-
Use
ShouldQueuefor listeners that perform slow operations. -
Use subscribers when one class needs to handle multiple events.
-
Use
Event::fake()to test event dispatching. -
Broadcasting extends events to real-time client communication.
-
Keep events small and listeners focused.
Laravel’s event system is especially valuable in applications where one action can trigger several independent operations. It improves separation of concerns and makes the application easier to maintain, test, and extend.