Laravel Event system overview
In a nutshell
Laravel’s event system provides a simple observer pattern implementation, allowing you to subscribe to various events that occur within your application and to take appropriate actions as needed.
Event classes are typically stored in the app/Events
directory, while their listeners are stored in app/Listeners
.
Why use events?
Events serve as a great way to decouple various aspects of your application, since a single event can have multiple listeners that do not depend on each other. For example, you may wish to send a Slack notification to your user each time an order has shipped. Instead of coupling your order processing code to your Slack notification code, you can raise an App\Events\OrderShipped
event which a listener can receive and use to dispatch a Slack notification.
Make event
php artisan make:event
Make listener
php artisan make:listener
Register events & listeners
Auto discovery
By default, Laravel will automatically find and register your event listeners by scanning your application’s Listeners
directory. When Laravel finds any listener class method that begins with handle
or __invoke
, Laravel will register those methods as event listeners for the event that is type-hinted in the method’s signature:
use App\Events\PodcastProcessed;
class SendPodcastNotification
{
/**
* Handle the given event.
*/
public function handle(PodcastProcessed $event): void
{
// ...
}
}
Manually register events
Using the Event
facade, you may manually register events and their corresponding listeners within the boot
method of your application’s AppServiceProvider
:
use App\Domain\Orders\Events\PodcastProcessed;
use App\Domain\Orders\Listeners\SendPodcastNotification;
use Illuminate\Support\Facades\Event;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Event::listen(
PodcastProcessed::class,
SendPodcastNotification::class,
);
}
List app events
php artisan event:list
Queue an event listener
To specify that a listener should be queued, add the ShouldQueue
interface to the listener class. Listeners generated by the make:listener
Artisan commands already have this interface imported into the current namespace so you can use it immediately:
<?php
namespace App\Listeners;
use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendShipmentNotification implements ShouldQueue
{
// ...
}
That’s it! Now, when an event handled by this listener is dispatched, the listener will automatically be queued by the event dispatcher using Laravel’s queue system. If no exceptions are thrown when the listener is executed by the queue, the queued job will automatically be deleted after it has finished processing.
Customizing The Queue Connection, Name, & Delay
<?php
namespace App\Listeners;
use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendShipmentNotification implements ShouldQueue
{
/**
* The name of the connection the job should be sent to.
*
* @var string|null
*/
public $connection = 'sqs';
/**
* The name of the queue the job should be sent to.
*
* @var string|null
*/
public $queue = 'listeners';
/**
* The time (seconds) before the job should be processed.
*
* @var int
*/
public $delay = 60;
}
Conditionally Queueing Listeners
Sometimes, you may need to determine whether a listener should be queued based on some data that are only available at runtime. To accomplish this, a shouldQueue
method may be added to a listener to determine whether the listener should be queued. If the shouldQueue
method returns false
, the listener will not be queued:
<?php
namespace App\Listeners;
use App\Events\OrderCreated;
use Illuminate\Contracts\Queue\ShouldQueue;
class RewardGiftCard implements ShouldQueue
{
/**
* Reward a gift card to the customer.
*/
public function handle(OrderCreated $event): void
{
// ...
}
/**
* Determine whether the listener should be queued.
*/
public function shouldQueue(OrderCreated $event): bool
{
return $event->order->subtotal >= 5000;
}
}
Queued Event Listeners and Database Transactions
See here.
Handle failed jobs
Sometimes your queued event listeners may fail. If the queued listener exceeds the maximum number of attempts as defined by your queue worker, the failed
method will be called on your listener. The failed
method receives the event instance and the Throwable
that caused the failure:
<?php
namespace App\Listeners;
use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Throwable;
class SendShipmentNotification implements ShouldQueue
{
use InteractsWithQueue;
/**
* Handle the event.
*/
public function handle(OrderShipped $event): void
{
// ...
}
/**
* Handle a job failure.
*/
public function failed(OrderShipped $event, Throwable $exception): void
{
// ...
}
}
Specify maximum attempts for queued listener
<?php
namespace App\Listeners;
use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class SendShipmentNotification implements ShouldQueue
{
use InteractsWithQueue;
/**
* The number of times the queued listener may be attempted.
*
* @var int
*/
public $tries = 5;
}
Specify maximum time duration for queued listener
use DateTime;
/**
* Determine the time at which the listener should timeout.
*/
public function retryUntil(): DateTime
{
return now()->addMinutes(5);
}
Dispatch event
To dispatch an event, you may call the static dispatch
method on the event. This method is made available on the event by the Illuminate\Foundation\Events\Dispatchable
trait. Any arguments passed to the dispatch
method will be passed to the event’s constructor:
<?php
namespace App\Http\Controllers;
use App\Events\OrderShipped;
use App\Http\Controllers\Controller;
use App\Models\Order;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class OrderShipmentController extends Controller
{
/**
* Ship the given order.
*/
public function store(Request $request): RedirectResponse
{
$order = Order::findOrFail($request->order_id);
// Order shipment logic...
OrderShipped::dispatch($order);
return redirect('/orders');
}
}
Ensure events are dispatched after database transactions have committed
Sometimes, you may want to instruct Laravel to only dispatch an event after the active database transaction has committed. To do so, you may implement the ShouldDispatchAfterCommit
interface on the event class.
This interface instructs Laravel to not dispatch the event until the current database transaction is committed. If the transaction fails, the event will be discarded. If no database transaction is in progress when the event is dispatched, the event will be dispatched immediately:
<?php
namespace App\Events;
use App\Models\Order;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Events\ShouldDispatchAfterCommit;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class OrderShipped implements ShouldDispatchAfterCommit
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* Create a new event instance.
*/
public function __construct(
public Order $order,
) {}
}
Write an Event Subscriber
Event subscribers are classes that may subscribe to multiple events from within the subscriber class itself, allowing you to define several event handlers within a single class. Subscribers should define a subscribe
method, which will be passed an event dispatcher instance. You may call the listen
method on the given dispatcher to register event listeners:
<?php
namespace App\Listeners;
use Illuminate\Auth\Events\Login;
use Illuminate\Auth\Events\Logout;
use Illuminate\Events\Dispatcher;
class UserEventSubscriber
{
/**
* Handle user login events.
*/
public function handleUserLogin(Login $event): void {}
/**
* Handle user logout events.
*/
public function handleUserLogout(Logout $event): void {}
/**
* Register the listeners for the subscriber.
*/
public function subscribe(Dispatcher $events): void
{
$events->listen(
Login::class,
[UserEventSubscriber::class, 'handleUserLogin']
);
$events->listen(
Logout::class,
[UserEventSubscriber::class, 'handleUserLogout']
);
}
}
Register Event Subscribers
After writing the subscriber, Laravel will automatically register handler methods within the subscriber if they follow Laravel’s event discovery conventions. Otherwise, you may manually register your subscriber using the subscribe
method of the Event
facade. Typically, this should be done within the boot
method of your application’s AppServiceProvider
:
<?php
namespace App\Providers;
use App\Listeners\UserEventSubscriber;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Event::subscribe(UserEventSubscriber::class);
}
}