60 KiB
Events
- Introduction
- Generating Events and Listeners
- Registering Events and Listeners
- Event Discovery
- Manually Registering Events
- Closure Listeners
- Defining Events
- Defining Listeners
- Queued Event Listeners
- Manually Interacting With the Queue
- Queued Event Listeners and Database Transactions
- Queued Listener Middleware
- Encrypted Queued Listeners
- Handling Failed Jobs
- Dispatching Events
- Dispatching Events After Database Transactions
- Deferring Events
- Event Subscribers
- Writing Event Subscribers
- Registering Event Subscribers
- Testing
- Faking a Subset of Events
- Scoped Events Fakes
Introduction
Laravel's events provide a simple observer pattern implementation, allowing
you to subscribe and listen for various events that occur within your
application. Event classes are typically stored in the app/Events directory,
while their listeners are stored in app/Listeners. Don't worry if you don't
see these directories in your application as they will be created for you as
you generate events and listeners using Artisan console commands.
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.
Generating Events and Listeners
To quickly generate events and listeners, you may use the make:event and
make:listener Artisan commands:
1php artisan make:event PodcastProcessed
2
3php artisan make:listener SendPodcastNotification --event=PodcastProcessed
php artisan make:event PodcastProcessed
php artisan make:listener SendPodcastNotification --event=PodcastProcessed
For convenience, you may also invoke the make:event and make:listener
Artisan commands without additional arguments. When you do so, Laravel will
automatically prompt you for the class name and, when creating a listener, the
event it should listen to:
1php artisan make:event
2
3php artisan make:listener
php artisan make:event
php artisan make:listener
Registering Events and Listeners
Event 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:
1use App\Events\PodcastProcessed;
2
3class SendPodcastNotification
4{
5 /**
6 * Handle the event.
7 */
8 public function handle(PodcastProcessed $event): void
9 {
10 // ...
11 }
12}
use App\Events\PodcastProcessed;
class SendPodcastNotification
{
/**
* Handle the event.
*/
public function handle(PodcastProcessed $event): void
{
// ...
}
}
You may listen to multiple events using PHP's union types:
1/**
2 * Handle the event.
3 */
4public function handle(PodcastProcessed|PodcastPublished $event): void
5{
6 // ...
7}
/**
* Handle the event.
*/
public function handle(PodcastProcessed|PodcastPublished $event): void
{
// ...
}
If you plan to store your listeners in a different directory or within
multiple directories, you may instruct Laravel to scan those directories using
the withEvents method in your application's bootstrap/app.php file:
1->withEvents(discover: [
2 __DIR__.'/../app/Domain/Orders/Listeners',
3])
->withEvents(discover: [
__DIR__.'/../app/Domain/Orders/Listeners',
])
You may scan for listeners in multiple similar directories using the *
character as a wildcard:
1->withEvents(discover: [
2 __DIR__.'/../app/Domain/*/Listeners',
3])
->withEvents(discover: [
__DIR__.'/../app/Domain/*/Listeners',
])
The event:list command may be used to list all of the listeners registered
within your application:
1php artisan event:list
php artisan event:list
Event Discovery in Production
To give your application a speed boost, you should cache a manifest of all of
your application's listeners using the optimize or event:cache Artisan
commands. Typically, this command should be run as part of your application's
deployment process. This manifest will
be used by the framework to speed up the event registration process. The
event:clear command may be used to destroy the event cache.
Manually Registering Events
Using the Event facade, you may manually register events and their
corresponding listeners within the boot method of your application's
AppServiceProvider:
1use App\Domain\Orders\Events\PodcastProcessed;
2use App\Domain\Orders\Listeners\SendPodcastNotification;
3use Illuminate\Support\Facades\Event;
4
5/**
6 * Bootstrap any application services.
7 */
8public function boot(): void
9{
10 Event::listen(
11 PodcastProcessed::class,
12 SendPodcastNotification::class,
13 );
14}
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,
);
}
The event:list command may be used to list all of the listeners registered
within your application:
1php artisan event:list
php artisan event:list
Closure Listeners
Typically, listeners are defined as classes; however, you may also manually
register closure-based event listeners in the boot method of your
application's AppServiceProvider:
1use App\Events\PodcastProcessed;
2use Illuminate\Support\Facades\Event;
3
4/**
5 * Bootstrap any application services.
6 */
7public function boot(): void
8{
9 Event::listen(function (PodcastProcessed $event) {
10 // ...
11 });
12}
use App\Events\PodcastProcessed;
use Illuminate\Support\Facades\Event;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Event::listen(function (PodcastProcessed $event) {
// ...
});
}
Queueable Anonymous Event Listeners
When registering closure-based event listeners, you may wrap the listener
closure within the Illuminate\Events\queueable function to instruct Laravel
to execute the listener using the queue:
1use App\Events\PodcastProcessed;
2use function Illuminate\Events\queueable;
3use Illuminate\Support\Facades\Event;
4
5/**
6 * Bootstrap any application services.
7 */
8public function boot(): void
9{
10 Event::listen(queueable(function (PodcastProcessed $event) {
11 // ...
12 }));
13}
use App\Events\PodcastProcessed;
use function Illuminate\Events\queueable;
use Illuminate\Support\Facades\Event;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Event::listen(queueable(function (PodcastProcessed $event) {
// ...
}));
}
Like queued jobs, you may use the onConnection, onQueue, and delay
methods to customize the execution of the queued listener:
1Event::listen(queueable(function (PodcastProcessed $event) {
2 // ...
3})->onConnection('redis')->onQueue('podcasts')->delay(now()->addSeconds(10)));
Event::listen(queueable(function (PodcastProcessed $event) {
// ...
})->onConnection('redis')->onQueue('podcasts')->delay(now()->addSeconds(10)));
If you would like to handle anonymous queued listener failures, you may
provide a closure to the catch method while defining the queueable
listener. This closure will receive the event instance and the Throwable
instance that caused the listener's failure:
1use App\Events\PodcastProcessed;
2use function Illuminate\Events\queueable;
3use Illuminate\Support\Facades\Event;
4use Throwable;
5
6Event::listen(queueable(function (PodcastProcessed $event) {
7 // ...
8})->catch(function (PodcastProcessed $event, Throwable $e) {
9 // The queued listener failed...
10}));
use App\Events\PodcastProcessed;
use function Illuminate\Events\queueable;
use Illuminate\Support\Facades\Event;
use Throwable;
Event::listen(queueable(function (PodcastProcessed $event) {
// ...
})->catch(function (PodcastProcessed $event, Throwable $e) {
// The queued listener failed...
}));
Wildcard Event Listeners
You may also register listeners using the * character as a wildcard
parameter, allowing you to catch multiple events on the same listener.
Wildcard listeners receive the event name as their first argument and the
entire event data array as their second argument:
1Event::listen('event.*', function (string $eventName, array $data) {
2 // ...
3});
Event::listen('event.*', function (string $eventName, array $data) {
// ...
});
Defining Events
An event class is essentially a data container which holds the information
related to the event. For example, let's assume an App\Events\OrderShipped
event receives an Eloquent ORM object:
1<?php
2
3namespace App\Events;
4
5use App\Models\Order;
6use Illuminate\Broadcasting\InteractsWithSockets;
7use Illuminate\Foundation\Events\Dispatchable;
8use Illuminate\Queue\SerializesModels;
9
10class OrderShipped
11{
12 use Dispatchable, InteractsWithSockets, SerializesModels;
13
14 /**
15 * Create a new event instance.
16 */
17 public function __construct(
18 public Order $order,
19 ) {}
20}
<?php
namespace App\Events;
use App\Models\Order;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class OrderShipped
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* Create a new event instance.
*/
public function __construct(
public Order $order,
) {}
}
As you can see, this event class contains no logic. It is a container for the
App\Models\Order instance that was purchased. The SerializesModels trait
used by the event will gracefully serialize any Eloquent models if the event
object is serialized using PHP's serialize function, such as when utilizing
queued listeners.
Defining Listeners
Next, let's take a look at the listener for our example event. Event listeners
receive event instances in their handle method. The make:listener Artisan
command, when invoked with the --event option, will automatically import the
proper event class and type-hint the event in the handle method. Within the
handle method, you may perform any actions necessary to respond to the
event:
1<?php
2
3namespace App\Listeners;
4
5use App\Events\OrderShipped;
6
7class SendShipmentNotification
8{
9 /**
10 * Create the event listener.
11 */
12 public function __construct() {}
13
14 /**
15 * Handle the event.
16 */
17 public function handle(OrderShipped $event): void
18 {
19 // Access the order using $event->order...
20 }
21}
<?php
namespace App\Listeners;
use App\Events\OrderShipped;
class SendShipmentNotification
{
/**
* Create the event listener.
*/
public function __construct() {}
/**
* Handle the event.
*/
public function handle(OrderShipped $event): void
{
// Access the order using $event->order...
}
}
Your event listeners may also type-hint any dependencies they need on their constructors. All event listeners are resolved via the Laravel service container, so dependencies will be injected automatically.
Stopping The Propagation Of An Event
Sometimes, you may wish to stop the propagation of an event to other
listeners. You may do so by returning false from your listener's handle
method.
Queued Event Listeners
Queueing listeners can be beneficial if your listener is going to perform a slow task such as sending an email or making an HTTP request. Before using queued listeners, make sure to configure your queue and start a queue worker on your server or local development environment.
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:
1<?php
2
3namespace App\Listeners;
4
5use App\Events\OrderShipped;
6use Illuminate\Contracts\Queue\ShouldQueue;
7
8class SendShipmentNotification implements ShouldQueue
9{
10 // ...
11}
<?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
If you would like to customize the queue connection, queue name, or queue
delay time of an event listener, you may define the $connection, $queue,
or $delay properties on your listener class:
1<?php
2
3namespace App\Listeners;
4
5use App\Events\OrderShipped;
6use Illuminate\Contracts\Queue\ShouldQueue;
7
8class SendShipmentNotification implements ShouldQueue
9{
10 /**
11 * The name of the connection the job should be sent to.
12 *
13 * @var string|null
14 */
15 public $connection = 'sqs';
16
17 /**
18 * The name of the queue the job should be sent to.
19 *
20 * @var string|null
21 */
22 public $queue = 'listeners';
23
24 /**
25 * The time (seconds) before the job should be processed.
26 *
27 * @var int
28 */
29 public $delay = 60;
30}
<?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;
}
If you would like to define the listener's queue connection, queue name, or
delay at runtime, you may define viaConnection, viaQueue, or withDelay
methods on the listener:
1/**
2 * Get the name of the listener's queue connection.
3 */
4public function viaConnection(): string
5{
6 return 'sqs';
7}
8
9/**
10 * Get the name of the listener's queue.
11 */
12public function viaQueue(): string
13{
14 return 'listeners';
15}
16
17/**
18 * Get the number of seconds before the job should be processed.
19 */
20public function withDelay(OrderShipped $event): int
21{
22 return $event->highPriority ? 0 : 60;
23}
/**
* Get the name of the listener's queue connection.
*/
public function viaConnection(): string
{
return 'sqs';
}
/**
* Get the name of the listener's queue.
*/
public function viaQueue(): string
{
return 'listeners';
}
/**
* Get the number of seconds before the job should be processed.
*/
public function withDelay(OrderShipped $event): int
{
return $event->highPriority ? 0 : 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:
1<?php
2
3namespace App\Listeners;
4
5use App\Events\OrderCreated;
6use Illuminate\Contracts\Queue\ShouldQueue;
7
8class RewardGiftCard implements ShouldQueue
9{
10 /**
11 * Reward a gift card to the customer.
12 */
13 public function handle(OrderCreated $event): void
14 {
15 // ...
16 }
17
18 /**
19 * Determine whether the listener should be queued.
20 */
21 public function shouldQueue(OrderCreated $event): bool
22 {
23 return $event->order->subtotal >= 5000;
24 }
25}
<?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;
}
}
Manually Interacting With the Queue
If you need to manually access the listener's underlying queue job's delete
and release methods, you may do so using the
Illuminate\Queue\InteractsWithQueue trait. This trait is imported by default
on generated listeners and provides access to these methods:
1<?php
2
3namespace App\Listeners;
4
5use App\Events\OrderShipped;
6use Illuminate\Contracts\Queue\ShouldQueue;
7use Illuminate\Queue\InteractsWithQueue;
8
9class SendShipmentNotification implements ShouldQueue
10{
11 use InteractsWithQueue;
12
13 /**
14 * Handle the event.
15 */
16 public function handle(OrderShipped $event): void
17 {
18 if (true) {
19 $this->release(30);
20 }
21 }
22}
<?php
namespace App\Listeners;
use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class SendShipmentNotification implements ShouldQueue
{
use InteractsWithQueue;
/**
* Handle the event.
*/
public function handle(OrderShipped $event): void
{
if (true) {
$this->release(30);
}
}
}
Queued Event Listeners and Database Transactions
When queued listeners are dispatched within database transactions, they may be processed by the queue before the database transaction has committed. When this happens, any updates you have made to models or database records during the database transaction may not yet be reflected in the database. In addition, any models or database records created within the transaction may not exist in the database. If your listener depends on these models, unexpected errors can occur when the job that dispatches the queued listener is processed.
If your queue connection's after_commit configuration option is set to
false, you may still indicate that a particular queued listener should be
dispatched after all open database transactions have been committed by
implementing the ShouldQueueAfterCommit interface on the listener class:
1<?php
2
3namespace App\Listeners;
4
5use Illuminate\Contracts\Queue\ShouldQueueAfterCommit;
6use Illuminate\Queue\InteractsWithQueue;
7
8class SendShipmentNotification implements ShouldQueueAfterCommit
9{
10 use InteractsWithQueue;
11}
<?php
namespace App\Listeners;
use Illuminate\Contracts\Queue\ShouldQueueAfterCommit;
use Illuminate\Queue\InteractsWithQueue;
class SendShipmentNotification implements ShouldQueueAfterCommit
{
use InteractsWithQueue;
}
To learn more about working around these issues, please review the documentation regarding queued jobs and database transactions.
Queued Listener Middleware
Queued listeners can also utilize [job middleware](/docs/12.x/queues#job-
middleware). Job middleware allow you to wrap custom logic around the
execution of queued listeners, reducing boilerplate in the listeners
themselves. After creating job middleware, they may be attached to a listener
by returning them from the listener's middleware method:
1<?php
2
3namespace App\Listeners;
4
5use App\Events\OrderShipped;
6use App\Jobs\Middleware\RateLimited;
7use Illuminate\Contracts\Queue\ShouldQueue;
8
9class SendShipmentNotification implements ShouldQueue
10{
11 /**
12 * Handle the event.
13 */
14 public function handle(OrderShipped $event): void
15 {
16 // Process the event...
17 }
18
19 /**
20 * Get the middleware the listener should pass through.
21 *
22 * @return array<int, object>
23 */
24 public function middleware(OrderShipped $event): array
25 {
26 return [new RateLimited];
27 }
28}
<?php
namespace App\Listeners;
use App\Events\OrderShipped;
use App\Jobs\Middleware\RateLimited;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendShipmentNotification implements ShouldQueue
{
/**
* Handle the event.
*/
public function handle(OrderShipped $event): void
{
// Process the event...
}
/**
* Get the middleware the listener should pass through.
*
* @return array<int, object>
*/
public function middleware(OrderShipped $event): array
{
return [new RateLimited];
}
}
Encrypted Queued Listeners
Laravel allows you to ensure the privacy and integrity of a queued listener's
data via encryption. To get started, simply add the
ShouldBeEncrypted interface to the listener class. Once this interface has
been added to the class, Laravel will automatically encrypt your listener
before pushing it onto a queue:
1<?php
2
3namespace App\Listeners;
4
5use App\Events\OrderShipped;
6use Illuminate\Contracts\Queue\ShouldBeEncrypted;
7use Illuminate\Contracts\Queue\ShouldQueue;
8
9class SendShipmentNotification implements ShouldQueue, ShouldBeEncrypted
10{
11 // ...
12}
<?php
namespace App\Listeners;
use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendShipmentNotification implements ShouldQueue, ShouldBeEncrypted
{
// ...
}
Handling 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:
1<?php
2
3namespace App\Listeners;
4
5use App\Events\OrderShipped;
6use Illuminate\Contracts\Queue\ShouldQueue;
7use Illuminate\Queue\InteractsWithQueue;
8use Throwable;
9
10class SendShipmentNotification implements ShouldQueue
11{
12 use InteractsWithQueue;
13
14 /**
15 * Handle the event.
16 */
17 public function handle(OrderShipped $event): void
18 {
19 // ...
20 }
21
22 /**
23 * Handle a job failure.
24 */
25 public function failed(OrderShipped $event, Throwable $exception): void
26 {
27 // ...
28 }
29}
<?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
{
// ...
}
}
Specifying Queued Listener Maximum Attempts
If one of your queued listeners is encountering an error, you likely do not want it to keep retrying indefinitely. Therefore, Laravel provides various ways to specify how many times or for how long a listener may be attempted.
You may define a tries property on your listener class to specify how many
times the listener may be attempted before it is considered to have failed:
1<?php
2
3namespace App\Listeners;
4
5use App\Events\OrderShipped;
6use Illuminate\Contracts\Queue\ShouldQueue;
7use Illuminate\Queue\InteractsWithQueue;
8
9class SendShipmentNotification implements ShouldQueue
10{
11 use InteractsWithQueue;
12
13 /**
14 * The number of times the queued listener may be attempted.
15 *
16 * @var int
17 */
18 public $tries = 5;
19}
<?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;
}
As an alternative to defining how many times a listener may be attempted
before it fails, you may define a time at which the listener should no longer
be attempted. This allows a listener to be attempted any number of times
within a given time frame. To define the time at which a listener should no
longer be attempted, add a retryUntil method to your listener class. This
method should return a DateTime instance:
1use DateTime;
2
3/**
4 * Determine the time at which the listener should timeout.
5 */
6public function retryUntil(): DateTime
7{
8 return now()->addMinutes(5);
9}
use DateTime;
/**
* Determine the time at which the listener should timeout.
*/
public function retryUntil(): DateTime
{
return now()->addMinutes(5);
}
If both retryUntil and tries are defined, Laravel gives precedence to the
retryUntil method.
Specifying Queued Listener Backoff
If you would like to configure how many seconds Laravel should wait before
retrying a listener that has encountered an exception, you may do so by
defining a backoff property on your listener class:
1/**
2 * The number of seconds to wait before retrying the queued listener.
3 *
4 * @var int
5 */
6public $backoff = 3;
/**
* The number of seconds to wait before retrying the queued listener.
*
* @var int
*/
public $backoff = 3;
If you require more complex logic for determining the listeners's backoff
time, you may define a backoff method on your listener class:
1/**
2 * Calculate the number of seconds to wait before retrying the queued listener.
3 */
4public function backoff(OrderShipped $event): int
5{
6 return 3;
7}
/**
* Calculate the number of seconds to wait before retrying the queued listener.
*/
public function backoff(OrderShipped $event): int
{
return 3;
}
You may easily configure "exponential" backoffs by returning an array of
backoff values from the backoff method. In this example, the retry delay
will be 1 second for the first retry, 5 seconds for the second retry, 10
seconds for the third retry, and 10 seconds for every subsequent retry if
there are more attempts remaining:
1/**
2 * Calculate the number of seconds to wait before retrying the queued listener.
3 *
4 * @return list<int>
5 */
6public function backoff(OrderShipped $event): array
7{
8 return [1, 5, 10];
9}
/**
* Calculate the number of seconds to wait before retrying the queued listener.
*
* @return list<int>
*/
public function backoff(OrderShipped $event): array
{
return [1, 5, 10];
}
Specifying Queued Listener Max Exceptions
Sometimes you may wish to specify that a queued listener may be attempted many
times, but should fail if the retries are triggered by a given number of
unhandled exceptions (as opposed to being released by the release method
directly). To accomplish this, you may define a maxExceptions property on
your listener class:
1<?php
2
3namespace App\Listeners;
4
5use App\Events\OrderShipped;
6use Illuminate\Contracts\Queue\ShouldQueue;
7use Illuminate\Queue\InteractsWithQueue;
8
9class SendShipmentNotification implements ShouldQueue
10{
11 use InteractsWithQueue;
12
13 /**
14 * The number of times the queued listener may be attempted.
15 *
16 * @var int
17 */
18 public $tries = 25;
19
20 /**
21 * The maximum number of unhandled exceptions to allow before failing.
22 *
23 * @var int
24 */
25 public $maxExceptions = 3;
26
27 /**
28 * Handle the event.
29 */
30 public function handle(OrderShipped $event): void
31 {
32 // Process the event...
33 }
34}
<?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 = 25;
/**
* The maximum number of unhandled exceptions to allow before failing.
*
* @var int
*/
public $maxExceptions = 3;
/**
* Handle the event.
*/
public function handle(OrderShipped $event): void
{
// Process the event...
}
}
In this example, the listener will be retried up to 25 times. However, the listener will fail if three unhandled exceptions are thrown by the listener.
Specifying Queued Listener Timeout
Often, you know roughly how long you expect your queued listeners to take. For
this reason, Laravel allows you to specify a "timeout" value. If a listener is
processing for longer than the number of seconds specified by the timeout
value, the worker processing the listener will exit with an error. You may
define the maximum number of seconds a listener should be allowed to run by
defining a timeout property on your listener class:
1<?php
2
3namespace App\Listeners;
4
5use App\Events\OrderShipped;
6use Illuminate\Contracts\Queue\ShouldQueue;
7
8class SendShipmentNotification implements ShouldQueue
9{
10 /**
11 * The number of seconds the listener can run before timing out.
12 *
13 * @var int
14 */
15 public $timeout = 120;
16}
<?php
namespace App\Listeners;
use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendShipmentNotification implements ShouldQueue
{
/**
* The number of seconds the listener can run before timing out.
*
* @var int
*/
public $timeout = 120;
}
If you would like to indicate that a listener should be marked as failed on
timeout, you may define the failOnTimeout property on the listener class:
1<?php
2
3namespace App\Listeners;
4
5use App\Events\OrderShipped;
6use Illuminate\Contracts\Queue\ShouldQueue;
7
8class SendShipmentNotification implements ShouldQueue
9{
10 /**
11 * Indicate if the listener should be marked as failed on timeout.
12 *
13 * @var bool
14 */
15 public $failOnTimeout = true;
16}
<?php
namespace App\Listeners;
use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendShipmentNotification implements ShouldQueue
{
/**
* Indicate if the listener should be marked as failed on timeout.
*
* @var bool
*/
public $failOnTimeout = true;
}
Dispatching Events
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:
1<?php
2
3namespace App\Http\Controllers;
4
5use App\Events\OrderShipped;
6use App\Models\Order;
7use Illuminate\Http\RedirectResponse;
8use Illuminate\Http\Request;
9
10class OrderShipmentController extends Controller
11{
12 /**
13 * Ship the given order.
14 */
15 public function store(Request $request): RedirectResponse
16 {
17 $order = Order::findOrFail($request->order_id);
18
19 // Order shipment logic...
20
21 OrderShipped::dispatch($order);
22
23 return redirect('/orders');
24 }
25}
<?php
namespace App\Http\Controllers;
use App\Events\OrderShipped;
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');
}
}
If you would like to conditionally dispatch an event, you may use the
dispatchIf and dispatchUnless methods:
1OrderShipped::dispatchIf($condition, $order);
2
3OrderShipped::dispatchUnless($condition, $order);
OrderShipped::dispatchIf($condition, $order);
OrderShipped::dispatchUnless($condition, $order);
When testing, it can be helpful to assert that certain events were dispatched without actually triggering their listeners. Laravel's built-in testing helpers make it a cinch.
Dispatching Events After Database Transactions
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:
1<?php
2
3namespace App\Events;
4
5use App\Models\Order;
6use Illuminate\Broadcasting\InteractsWithSockets;
7use Illuminate\Contracts\Events\ShouldDispatchAfterCommit;
8use Illuminate\Foundation\Events\Dispatchable;
9use Illuminate\Queue\SerializesModels;
10
11class OrderShipped implements ShouldDispatchAfterCommit
12{
13 use Dispatchable, InteractsWithSockets, SerializesModels;
14
15 /**
16 * Create a new event instance.
17 */
18 public function __construct(
19 public Order $order,
20 ) {}
21}
<?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,
) {}
}
Deferring Events
Deferred events allow you to delay the dispatching of model events and execution of event listeners until after a specific block of code has completed. This is particularly useful when you need to ensure that all related records are created before event listeners are triggered.
To defer events, provide a closure to the Event::defer() method:
1use App\Models\User;
2use Illuminate\Support\Facades\Event;
3
4Event::defer(function () {
5 $user = User::create(['name' => 'Victoria Otwell']);
6
7 $user->posts()->create(['title' => 'My first post!']);
8});
use App\Models\User;
use Illuminate\Support\Facades\Event;
Event::defer(function () {
$user = User::create(['name' => 'Victoria Otwell']);
$user->posts()->create(['title' => 'My first post!']);
});
All events triggered within the closure will be dispatched after the closure is executed. This ensures that event listeners have access to all related records that were created during the deferred execution. If an exception occurs within the closure, the deferred events will not be dispatched.
To defer only specific events, pass an array of events as the second argument
to the defer method:
1use App\Models\User;
2use Illuminate\Support\Facades\Event;
3
4Event::defer(function () {
5 $user = User::create(['name' => 'Victoria Otwell']);
6
7 $user->posts()->create(['title' => 'My first post!']);
8}, ['eloquent.created: '.User::class]);
use App\Models\User;
use Illuminate\Support\Facades\Event;
Event::defer(function () {
$user = User::create(['name' => 'Victoria Otwell']);
$user->posts()->create(['title' => 'My first post!']);
}, ['eloquent.created: '.User::class]);
Event Subscribers
Writing Event Subscribers
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 receives an event dispatcher instance. You may call the listen
method on the given dispatcher to register event listeners:
1<?php
2
3namespace App\Listeners;
4
5use Illuminate\Auth\Events\Login;
6use Illuminate\Auth\Events\Logout;
7use Illuminate\Events\Dispatcher;
8
9class UserEventSubscriber
10{
11 /**
12 * Handle user login events.
13 */
14 public function handleUserLogin(Login $event): void {}
15
16 /**
17 * Handle user logout events.
18 */
19 public function handleUserLogout(Logout $event): void {}
20
21 /**
22 * Register the listeners for the subscriber.
23 */
24 public function subscribe(Dispatcher $events): void
25 {
26 $events->listen(
27 Login::class,
28 [UserEventSubscriber::class, 'handleUserLogin']
29 );
30
31 $events->listen(
32 Logout::class,
33 [UserEventSubscriber::class, 'handleUserLogout']
34 );
35 }
36}
<?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']
);
}
}
If your event listener methods are defined within the subscriber itself, you
may find it more convenient to return an array of events and method names from
the subscriber's subscribe method. Laravel will automatically determine the
subscriber's class name when registering the event listeners:
1<?php
2
3namespace App\Listeners;
4
5use Illuminate\Auth\Events\Login;
6use Illuminate\Auth\Events\Logout;
7use Illuminate\Events\Dispatcher;
8
9class UserEventSubscriber
10{
11 /**
12 * Handle user login events.
13 */
14 public function handleUserLogin(Login $event): void {}
15
16 /**
17 * Handle user logout events.
18 */
19 public function handleUserLogout(Logout $event): void {}
20
21 /**
22 * Register the listeners for the subscriber.
23 *
24 * @return array<string, string>
25 */
26 public function subscribe(Dispatcher $events): array
27 {
28 return [
29 Login::class => 'handleUserLogin',
30 Logout::class => 'handleUserLogout',
31 ];
32 }
33}
<?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.
*
* @return array<string, string>
*/
public function subscribe(Dispatcher $events): array
{
return [
Login::class => 'handleUserLogin',
Logout::class => 'handleUserLogout',
];
}
}
Registering 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:
1<?php
2
3namespace App\Providers;
4
5use App\Listeners\UserEventSubscriber;
6use Illuminate\Support\Facades\Event;
7use Illuminate\Support\ServiceProvider;
8
9class AppServiceProvider extends ServiceProvider
10{
11 /**
12 * Bootstrap any application services.
13 */
14 public function boot(): void
15 {
16 Event::subscribe(UserEventSubscriber::class);
17 }
18}
<?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);
}
}
Testing
When testing code that dispatches events, you may wish to instruct Laravel to
not actually execute the event's listeners, since the listener's code can be
tested directly and separately of the code that dispatches the corresponding
event. Of course, to test the listener itself, you may instantiate a listener
instance and invoke the handle method directly in your test.
Using the Event facade's fake method, you may prevent listeners from
executing, execute the code under test, and then assert which events were
dispatched by your application using the assertDispatched,
assertNotDispatched, and assertNothingDispatched methods:
Pest PHPUnit
1<?php
2
3use App\Events\OrderFailedToShip;
4use App\Events\OrderShipped;
5use Illuminate\Support\Facades\Event;
6
7test('orders can be shipped', function () {
8 Event::fake();
9
10 // Perform order shipping...
11
12 // Assert that an event was dispatched...
13 Event::assertDispatched(OrderShipped::class);
14
15 // Assert an event was dispatched twice...
16 Event::assertDispatched(OrderShipped::class, 2);
17
18 // Assert an event was dispatched once...
19 Event::assertDispatchedOnce(OrderShipped::class);
20
21 // Assert an event was not dispatched...
22 Event::assertNotDispatched(OrderFailedToShip::class);
23
24 // Assert that no events were dispatched...
25 Event::assertNothingDispatched();
26});
<?php
use App\Events\OrderFailedToShip;
use App\Events\OrderShipped;
use Illuminate\Support\Facades\Event;
test('orders can be shipped', function () {
Event::fake();
// Perform order shipping...
// Assert that an event was dispatched...
Event::assertDispatched(OrderShipped::class);
// Assert an event was dispatched twice...
Event::assertDispatched(OrderShipped::class, 2);
// Assert an event was dispatched once...
Event::assertDispatchedOnce(OrderShipped::class);
// Assert an event was not dispatched...
Event::assertNotDispatched(OrderFailedToShip::class);
// Assert that no events were dispatched...
Event::assertNothingDispatched();
});
1<?php
2
3namespace Tests\Feature;
4
5use App\Events\OrderFailedToShip;
6use App\Events\OrderShipped;
7use Illuminate\Support\Facades\Event;
8use Tests\TestCase;
9
10class ExampleTest extends TestCase
11{
12 /**
13 * Test order shipping.
14 */
15 public function test_orders_can_be_shipped(): void
16 {
17 Event::fake();
18
19 // Perform order shipping...
20
21 // Assert that an event was dispatched...
22 Event::assertDispatched(OrderShipped::class);
23
24 // Assert an event was dispatched twice...
25 Event::assertDispatched(OrderShipped::class, 2);
26
27 // Assert an event was dispatched once...
28 Event::assertDispatchedOnce(OrderShipped::class);
29
30 // Assert an event was not dispatched...
31 Event::assertNotDispatched(OrderFailedToShip::class);
32
33 // Assert that no events were dispatched...
34 Event::assertNothingDispatched();
35 }
36}
<?php
namespace Tests\Feature;
use App\Events\OrderFailedToShip;
use App\Events\OrderShipped;
use Illuminate\Support\Facades\Event;
use Tests\TestCase;
class ExampleTest extends TestCase
{
/**
* Test order shipping.
*/
public function test_orders_can_be_shipped(): void
{
Event::fake();
// Perform order shipping...
// Assert that an event was dispatched...
Event::assertDispatched(OrderShipped::class);
// Assert an event was dispatched twice...
Event::assertDispatched(OrderShipped::class, 2);
// Assert an event was dispatched once...
Event::assertDispatchedOnce(OrderShipped::class);
// Assert an event was not dispatched...
Event::assertNotDispatched(OrderFailedToShip::class);
// Assert that no events were dispatched...
Event::assertNothingDispatched();
}
}
You may pass a closure to the assertDispatched or assertNotDispatched
methods in order to assert that an event was dispatched that passes a given
"truth test". If at least one event was dispatched that passes the given truth
test then the assertion will be successful:
1Event::assertDispatched(function (OrderShipped $event) use ($order) {
2 return $event->order->id === $order->id;
3});
Event::assertDispatched(function (OrderShipped $event) use ($order) {
return $event->order->id === $order->id;
});
If you would simply like to assert that an event listener is listening to a
given event, you may use the assertListening method:
1Event::assertListening(
2 OrderShipped::class,
3 SendShipmentNotification::class
4);
Event::assertListening(
OrderShipped::class,
SendShipmentNotification::class
);
After calling Event::fake(), no event listeners will be executed. So, if
your tests use model factories that rely on events, such as creating a UUID
during a model's creating event, you should call Event::fake() after
using your factories.
Faking a Subset of Events
If you only want to fake event listeners for a specific set of events, you may
pass them to the fake or fakeFor method:
Pest PHPUnit
1test('orders can be processed', function () {
2 Event::fake([
3 OrderCreated::class,
4 ]);
5
6 $order = Order::factory()->create();
7
8 Event::assertDispatched(OrderCreated::class);
9
10 // Other events are dispatched as normal...
11 $order->update([
12 // ...
13 ]);
14});
test('orders can be processed', function () {
Event::fake([
OrderCreated::class,
]);
$order = Order::factory()->create();
Event::assertDispatched(OrderCreated::class);
// Other events are dispatched as normal...
$order->update([
// ...
]);
});
1/**
2 * Test order process.
3 */
4public function test_orders_can_be_processed(): void
5{
6 Event::fake([
7 OrderCreated::class,
8 ]);
9
10 $order = Order::factory()->create();
11
12 Event::assertDispatched(OrderCreated::class);
13
14 // Other events are dispatched as normal...
15 $order->update([
16 // ...
17 ]);
18}
/**
* Test order process.
*/
public function test_orders_can_be_processed(): void
{
Event::fake([
OrderCreated::class,
]);
$order = Order::factory()->create();
Event::assertDispatched(OrderCreated::class);
// Other events are dispatched as normal...
$order->update([
// ...
]);
}
You may fake all events except for a set of specified events using the
except method:
1Event::fake()->except([
2 OrderCreated::class,
3]);
Event::fake()->except([
OrderCreated::class,
]);
Scoped Event Fakes
If you only want to fake event listeners for a portion of your test, you may
use the fakeFor method:
Pest PHPUnit
1<?php
2
3use App\Events\OrderCreated;
4use App\Models\Order;
5use Illuminate\Support\Facades\Event;
6
7test('orders can be processed', function () {
8 $order = Event::fakeFor(function () {
9 $order = Order::factory()->create();
10
11 Event::assertDispatched(OrderCreated::class);
12
13 return $order;
14 });
15
16 // Events are dispatched as normal and observers will run...
17 $order->update([
18 // ...
19 ]);
20});
<?php
use App\Events\OrderCreated;
use App\Models\Order;
use Illuminate\Support\Facades\Event;
test('orders can be processed', function () {
$order = Event::fakeFor(function () {
$order = Order::factory()->create();
Event::assertDispatched(OrderCreated::class);
return $order;
});
// Events are dispatched as normal and observers will run...
$order->update([
// ...
]);
});
1<?php
2
3namespace Tests\Feature;
4
5use App\Events\OrderCreated;
6use App\Models\Order;
7use Illuminate\Support\Facades\Event;
8use Tests\TestCase;
9
10class ExampleTest extends TestCase
11{
12 /**
13 * Test order process.
14 */
15 public function test_orders_can_be_processed(): void
16 {
17 $order = Event::fakeFor(function () {
18 $order = Order::factory()->create();
19
20 Event::assertDispatched(OrderCreated::class);
21
22 return $order;
23 });
24
25 // Events are dispatched as normal and observers will run...
26 $order->update([
27 // ...
28 ]);
29 }
30}
<?php
namespace Tests\Feature;
use App\Events\OrderCreated;
use App\Models\Order;
use Illuminate\Support\Facades\Event;
use Tests\TestCase;
class ExampleTest extends TestCase
{
/**
* Test order process.
*/
public function test_orders_can_be_processed(): void
{
$order = Event::fakeFor(function () {
$order = Order::factory()->create();
Event::assertDispatched(OrderCreated::class);
return $order;
});
// Events are dispatched as normal and observers will run...
$order->update([
// ...
]);
}
}