2828 lines
60 KiB
Markdown
2828 lines
60 KiB
Markdown
# 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](/docs/12.x/deployment#optimization). 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](/docs/12.x/queues):
|
||
|
||
|
||
|
||
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](/docs/12.x/eloquent) 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](/docs/12.x/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](/docs/12.x/queues) 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](/docs/12.x/queues). 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](/docs/12.x/queues#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](/docs/12.x/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([
|
||
// ...
|
||
]);
|
||
}
|
||
}
|
||
|