event


introduce

Larave's events provide a simple observer pattern implementation, allowing you to subscribe to and listen to various events occurring in the application. Event classes are usually stored in app/Events Directory, and their listeners are stored in app/Listeners Directory. If you can't see these directories in your application, don't worry. When you use Artisan console commands to generate events and listeners, they will be created for you.

Events are a good way to decouple all aspects of an application, because a single event can have multiple listeners that do not depend on each other. For example, each time an order is shipped, you can want to send a Slack notification to the user. Instead of coupling the order processing code with the Slack notification code, you can trigger a App\Events\OrderShipped Event, the listener can receive and use it to send Slack notifications.

Register events and listeners

The App\Providers\EventServiceProvider Provides a convenient location to register event listeners for all applications. The listener property contains an array of all events (keys) and their listeners (values). You can add as many events to this array as your application needs. For example, let's add a OrderShipped event:

 use App\Events\OrderShipped; use App\Listeners\SendShipmentNotification; /** * The event listener mappings for the application. * * @var array<class-string,  array<int, class-string>> */ protected $listen = [ OrderShipped::class => [ SendShipmentNotification::class, ], ];

event:list The command can be used to display a list of all events and listeners registered by your application.

Generate events and listeners

Of course, manually creating files for each event and listener is tedious. Instead, add listeners and events to your EventServiceProvider And use event:generate Artisan command. This command will be generated in the EventServiceProvider Any events or listeners listed in but not yet present:

 php artisan event:generate

Alternatively, you can use the make:event and make:listener Artisan commands generate a single event and listener:

 php artisan make:event PodcastProcessed php artisan make:listener SendPodcastNotification --event=PodcastProcessed

Manually register events

In general, events should be EventServiceProvider $listen Array registration; However, you can also visit EventServiceProvider Of boot Manually register event listeners based on classes or closures in the method:

 use App\Events\PodcastProcessed; use App\Listeners\SendPodcastNotification; use Illuminate\Support\Facades\Event; /** * Register any other events for your application. */ public function boot(): void { Event::listen( PodcastProcessed::class, [SendPodcastNotification::class, 'handle'] ); Event::listen(function (PodcastProcessed $event) { // ... }); }

Queueable Anonymous Event Listener

When manually registering closure based event listeners, you can Illuminate\Events\queueable Wrap the listener closure in the function to instruct Laravel to use queue Execution Listener:

 use App\Events\PodcastProcessed; use function Illuminate\Events\queueable; use Illuminate\Support\Facades\Event; /** * Register any other events for your application. */ public function boot(): void { Event::listen(queueable(function (PodcastProcessed $event) { // ... })); }

Like queued tasks, you can use the onConnection onQueue and delay Method to customize the execution of queue listeners:

 Event::listen(queueable(function (PodcastProcessed $event) { // ... })->onConnection('redis')->onQueue('podcasts')->delay(now()->addSeconds(10)));

If you want to fail to process anonymous queued listeners, you can send the catch Method provides a closure. This closure will receive the event instance and the Throwable example:

 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 Listener

You can even use * Register listeners as wildcard parameters so that you can capture multiple events on the same listener. The wildcard listener takes the event name as the first parameter and the entire event data array as the second parameter:

 Event::listen('event.*', function (string $eventName, array $data) { // ... });

Event discovery

You can enable automatic event discovery instead of EventServiceProvider Of $listen Manually register events and listeners in the array. When event discovery is enabled, Larave will scan the application's Listeners The directory automatically finds and registers events and listeners. In addition, EventServiceProvider Any events explicitly defined in will still be registered.

Larave uses PHP's reflection service to scan the listener class to find the event listener. When Laravel found that any handle or __invoke When starting listener class methods, Larvel will register these methods as event listeners to the events specified in the method signature:

 use App\Events\PodcastProcessed; class SendPodcastNotification { /** * Handle the given event. */ public function handle(PodcastProcessed $event): void { // ... } }

By default, event discovery is disabled, but you can override the EventServiceProvider Of shouldDiscoverEvents Method to enable it:

 /** * Determine if events and listeners should be automatically discovered. */ public function shouldDiscoverEvents(): bool { return true; }

By default, the applied app/Listeners All listeners in the directory will be scanned. If you want to define additional directories for scanning, you can set them in your EventServiceProvider Overwrite in discoverEventsWithin method:

 /** * Get the listener directories that should be used to discover events. * * @return array<int, string> */ protected function discoverEventsWithin(): array { return [ $this->app->path('Listeners'), ]; }

Production environment event discovery

In a production environment, it is not efficient to scan all listeners on each request. Therefore, during deployment, you should run event:cache Artisan command to cache the list of all events and listeners of the application. The framework will use this manifest to speed up the event registration process. have access to event:clear Command to destroy the cache.

Define Events

The event class is essentially a data container used to store information related to events. For example, suppose App\Events\OrderShipped The event receives a Eloquent ORM Object:

 <? 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 save purchased App\Models\Order The container of the instance. If you use PHP's serialize Function to serialize the event object, such as using Queued listeners When the event uses SerializesModels Features will elegantly serialize any Eloquent model.

Define Listener

Next, let's look at the listener of the sample event. Event listeners in their handle Method. event:generate and make:listener The Artisan command will automatically import the correct event class and handle Method to prompt the event type. stay handle Method, you can perform any action required to respond to the event:

 <? 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 can also specify any dependencies they need in their constructors. All event listeners pass Laravel Service Container Resolves, so dependencies are automatically injected.

Stop propagation of events

Sometimes, you may want to stop the event propagation to other listeners. You can click the handle Method false To achieve.

Queued event listeners

If your listener needs to perform time-consuming tasks such as sending e-mail or making HTTP requests, queuing the listener may be beneficial. Before using queued listeners, make sure that Configure Queue And start queue workers on the server or in the local development environment.

To specify queuing listeners, set ShouldQueue The interface is added to the listener class. from event:generate and make:listener The listener generated by Artisan command has been imported into the current namespace, so you can use it immediately:

 <? php namespace App\Listeners; use App\Events\OrderShipped; use Illuminate\Contracts\Queue\ShouldQueue; class SendShipmentNotification implements ShouldQueue { // ... }

That's it. Now, when the event processed by this listener is dispatched, the event scheduler will use the Queue system Automatically queue listeners. If the listener does not throw any exception when executing in the queue, the queued job will be automatically deleted after processing.

Customize queue connections, names, and delays

If you want to customize the queue connection, queue name or queue delay time of the event listener, you can define it in the listener class $connection $queue or $delay Properties:

 <? 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 want to define the queue connection, queue name or delay time of the listener at runtime, you can define it on the listener viaConnection viaQueue or withDelay method:

 <? php namespace App\Listeners; use App\Events\OrderShipped; use Illuminate\Contracts\Queue\ShouldQueue; class SendShipmentNotification implements ShouldQueue { /** * The name of the connection the job should be sent to. * * @var string|null */ public $connection = 'sqs'; /** * The name of the queue the job should be sent to. * * @var string|null */ public $queue = 'listeners'; /** * The time (seconds) before the job should be processed. * * @var int */ public $delay = 60; }

Conditionally queued listeners

Sometimes, you may need to determine whether to queue listeners based on some data that is only available at runtime. To achieve this, you can add shouldQueue Method to determine whether listeners should be queued. If shouldQueue Method return false , the listener will not be executed:

 <? 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; } }

Manual interaction with queues

If you need to manually access the underlying queue jobs of the listener delete and release Method, you can use Illuminate\Queue\InteractsWithQueue characteristic. This attribute has imported the generated listeners by default and provided access to these methods:

 <? 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 queue listeners are scheduled in a database transaction, they may be queued before the database transaction is committed. When this happens, any updates to the model or database records during a database transaction may not have been reflected in the database. In addition, any model or database record created in the transaction may not exist in the database. If your listener depends on these models, unexpected errors may be caused when processing jobs in the scheduling queue.

If your queue is connected to a after_commit The configuration option is set to false , you can still instruct a specific queued listener to schedule after all open database transactions are committed:

 <? php namespace App\Listeners; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Queue\InteractsWithQueue; class SendShipmentNotification implements ShouldQueue { use InteractsWithQueue; public $afterCommit = true; }

To learn more about solving these problems, see the Documents for queued jobs and database transactions

Processing failed jobs

Sometimes, queued event listeners may fail. If the queued listener exceeds the maximum number of attempts defined by the queue worker, the listener will be called on the failed method. failed Method receives the event instance and the Throwable

 <? php namespace App\Listeners; use App\Events\OrderShipped; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Queue\InteractsWithQueue; use Throwable; class SendShipmentNotification implements ShouldQueue { use InteractsWithQueue; /** * Handle the event. */ public function handle(OrderShipped $event): void { // ... } /** * Handle a job failure. */ public function failed(OrderShipped $event,  Throwable $exception): void { // ... } }

Specify the maximum number of attempts to queue listeners

If one of your queuing listeners encounters an error, you may not want it to keep retrying. Therefore, Laravel provides several methods to specify when or how long the listener will try.

You can define the $tries Property to specify how many attempts can be made before the listener is considered to fail:

 <? 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 can fail before trying, you can define when the listener will not try again. This allows listeners to be tried any number of times in a given time range. To define when to stop trying listeners, add a retryUntil method. This method should return a DateTime example:

 use DateTime; /** * Determine the time at which the listener should timeout. */ public function retryUntil(): DateTime { return now()->addMinutes(5); }

Event scheduling

To schedule an event, you can call static dispatch method. This method is determined by Illuminate\Foundation\Events\Dispatchable Provided by the feature. Pass to dispatch Any parameter of the method will be passed to the constructor of the event:

 <? php namespace App\Http\Controllers; use App\Events\OrderShipped; use App\Http\Controllers\Controller; use App\Models\Order; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; class OrderShipmentController extends Controller { /** * Ship the given order. */ public function store(Request $request): RedirectResponse { $order = Order::findOrFail($request->order_id); // Order shipment logic... OrderShipped::dispatch($order); return redirect('/orders'); } }

If you want to conditionally schedule an event, you can use the dispatchIf and dispatchUnless method:

 OrderShipped::dispatchIf($condition, $order); OrderShipped::dispatchUnless($condition, $order);

When testing, you can help test by asserting that certain events are scheduled without actually triggering their listeners. Laravel Built in test assistant Make it a breeze.

Event Subscriber

Write event subscriber

Event subscribers are classes that can subscribe to multiple events from the subscriber class itself, enabling you to define multiple event handlers in a single class. The subscriber should define a subscribe Method, which passes an instance of the event scheduler. You can call the listen Method to register an event listener:

 <? 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(string $event): void {} /** * Handle user logout events. */ public function handleUserLogout(string $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 the event listener method is defined in the subscriber itself, you may find it more convenient to return an array of event and method names from the subscriber's subscription method. When registering an event listener, Larvel will automatically determine the subscriber's class name:

 <? 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(string $event): void {} /** * Handle user logout events. */ public function handleUserLogout(string $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', ]; } }

Register event subscribers

After writing subscribers, you can register them with the event scheduler. You can use EventServiceProvider On $subscribe Property to register subscribers. For example, let's put UserEventSubscriber Add to list:

 <? php namespace App\Providers; use App\Listeners\UserEventSubscriber; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; class EventServiceProvider extends ServiceProvider { /** * The event listener mappings for the application. * * @var array */ protected $listen = [ // ... ]; /** * The subscriber classes to register. * * @var array */ protected $subscribe = [ UserEventSubscriber::class, ]; }

test

When testing the code that dispatches events, you may want to instruct Laravel not to actually execute the event's listener, because the listener's code can be tested directly and separated from the code that dispatches the corresponding event. Of course, to test the listener itself, you can instantiate the listener instance in the test and directly call handle method.

use Event Facade fake Method, you can prevent the listener from executing, execute the test code, and then use the assertDispatched assertNotDispatched and assertNothingDispatched Method to assert which events are dispatched by the application:

 <? 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 not dispatched... Event::assertNotDispatched(OrderFailedToShip::class); // Assert that no events were dispatched... Event::assertNothingDispatched(); } }

You can pass a closure to assertDispatched or assertNotDispatched Method to assert that the given "truth test" has been sent. If at least one event passing the given truth test is dispatched, the assertion will succeed:

 Event::assertDispatched(function (OrderShipped $event) use ($order) { return $event->order->id === $order->id; });

If you only want to assert that the event listener is listening to a given event, you can use assertListening method:

 Event::assertListening( OrderShipped::class, SendShipmentNotification::class );

call Event::fake() No event listener will be executed after. Therefore, if your test uses an event dependent model factory, such as creating a UUID during model creation events, you should call the Event::fake()

A subset of forgery events

If you only want to forge event listeners for a specific set of events, you can pass them to fake or fakeFor method:

 /** * 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 can use except Method forges all events except a set of specified events:

 Event::fake()->except([ OrderCreated::class, ]);

Scope event forgery

If you only want to forge event listeners in part of the test, you can use fakeFor method:

 <? 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([...]); } }

give the thumbs-up Cancel Like Collection Cancel Collection

<<Previous: contract

>>Next: file store