Події — Laravel
Події (events) Laravel — це проста реалізація патерна «спостерігач» (observer), яка дозволяє підписуватися на різні події, що відбуваються у вашому застосунку, і слухати їх. Класи подій зазвичай зберігаються в теці app/Events, а їхні слухачі — у app/Listeners. Не хвилюйтеся, якщо ви не бачите цих тек у своєму застосунку: вони будуть створені автоматично, коли ви генеруватимете події та слухачів за допомогою консольних команд Artisan.
Події — чудовий спосіб розчепити різні аспекти вашого застосунку, оскільки одна подія може мати кілька слухачів, які не залежать один від одного. Наприклад, ви можете надсилати користувачеві сповіщення в Slack щоразу, коли замовлення відправлено. Замість того щоб зчіплювати код обробки замовлень із кодом сповіщень у Slack, ви можете згенерувати подію App\Events\OrderShipped, яку слухач отримає й використає, щоб відправити сповіщення в Slack.
Генерація подій і слухачів
Щоб швидко згенерувати події та слухачів, ви можете скористатися Artisan-командами make:event і make:listener:
php artisan make:event PodcastProcessed
php artisan make:listener SendPodcastNotification --event=PodcastProcessed
Для зручності ви також можете викликати Artisan-команди make:event і make:listener без додаткових аргументів. Тоді Laravel автоматично запитає у вас імʼя класу, а при створенні слухача — подію, яку він має слухати:
php artisan make:event
php artisan make:listener
Реєстрація подій і слухачів
Виявлення подій
За замовчуванням Laravel автоматично знаходить і реєструє слухачів ваших подій, скануючи теку Listeners вашого застосунку. Коли Laravel знаходить у класі слухача метод, імʼя якого починається з handle або __invoke, він реєструє ці методи як слухачів події, тип якої вказано в сигнатурі методу:
use App\Events\PodcastProcessed;
class SendPodcastNotification
{
/**
* Handle the event.
*/
public function handle(PodcastProcessed $event): void
{
// ...
}
}
Ви можете слухати кілька подій, використовуючи union-типи PHP:
/**
* Handle the event.
*/
public function handle(PodcastProcessed|PodcastPublished $event): void
{
// ...
}
Якщо ви плануєте зберігати слухачів в іншій теці або в кількох теках, ви можете вказати Laravel сканувати ці теки за допомогою методу withEvents у файлі bootstrap/app.php вашого застосунку:
->withEvents(discover: [
__DIR__.'/../app/Domain/Orders/Listeners',
])
Ви можете шукати слухачів у кількох подібних теках, використовуючи символ * як шаблон:
->withEvents(discover: [
__DIR__.'/../app/Domain/*/Listeners',
])
Команду event:list можна використати, щоб вивести всіх слухачів, зареєстрованих у вашому застосунку:
php artisan event:list
Виявлення подій у продакшені
Щоб пришвидшити ваш застосунок, вам слід закешувати маніфест усіх слухачів застосунку за допомогою Artisan-команд optimize або event:cache. Зазвичай цю команду варто запускати як частину процесу розгортання вашого застосунку. Цей маніфест використовуватиметься фреймворком, щоб прискорити процес реєстрації подій. Команду event:clear можна використати, щоб знищити кеш подій.
Динамічне виявлення подій
Щоб динамічно керувати тим, чи буде виявлено конкретного слухача, ви можете реалізувати інтерфейс ShouldBeDiscovered у класі слухача й визначити метод shouldBeDiscovered, який повертає булеве значення. Якщо метод повертає false, слухач не буде зареєстровано під час виявлення подій:
use Illuminate\Contracts\Events\ShouldBeDiscovered;
class SendPodcastNotification implements ShouldBeDiscovered
{
/**
* Handle the event.
*/
public function handle(PodcastProcessed $event): void
{
// ...
}
/**
* Determine if the listener should be discovered.
*/
public static function shouldBeDiscovered(): bool
{
return app()->environment('production');
}
}
Ручна реєстрація подій
За допомогою фасаду Event ви можете вручну зареєструвати події та відповідних слухачів у методі boot вашого AppServiceProvider:
use App\Domain\Orders\Events\PodcastProcessed;
use App\Domain\Orders\Listeners\SendPodcastNotification;
use Illuminate\Support\Facades\Event;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Event::listen(
PodcastProcessed::class,
SendPodcastNotification::class,
);
}
Команду event:list можна використати, щоб вивести всіх слухачів, зареєстрованих у вашому застосунку:
php artisan event:list
Слухачі-замикання
Зазвичай слухачі визначаються як класи; проте ви також можете вручну зареєструвати слухачів подій на основі замикань у методі boot вашого AppServiceProvider:
use App\Events\PodcastProcessed;
use Illuminate\Support\Facades\Event;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Event::listen(function (PodcastProcessed $event) {
// ...
});
}
Анонімні слухачі подій у черзі
Реєструючи слухачів подій на основі замикань, ви можете обгорнути замикання слухача у функцію Illuminate\Events\queueable, щоб вказати Laravel виконувати слухача через чергу:
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) {
// ...
}));
}
Як і для завдань (jobs) у черзі, ви можете використовувати методи onConnection, onQueue і delay, щоб налаштувати виконання слухача в черзі:
Event::listen(queueable(function (PodcastProcessed $event) {
// ...
})->onConnection('redis')->onQueue('podcasts')->delay(now()->plus(seconds: 10)));
Якщо ви хочете обробляти збої анонімних слухачів у черзі, ви можете передати замикання в метод catch при визначенні queueable-слухача. Це замикання отримає екземпляр події та екземпляр Throwable, який спричинив збій слухача:
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) {
// Слухач у черзі завершився збоєм...
}));
Слухачі подій із шаблоном
Ви також можете реєструвати слухачів, використовуючи символ * як параметр-шаблон, що дозволяє перехоплювати кілька подій одним слухачем. Слухачі з шаблоном отримують імʼя події першим аргументом і весь масив даних події другим аргументом:
Event::listen('event.*', function (string $eventName, array $data) {
// ...
});
Визначення подій
Клас події — це по суті контейнер даних, який містить інформацію, повʼязану з подією. Наприклад, припустімо, що подія App\Events\OrderShipped отримує обʼєкт Eloquent ORM:
<?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,
) {}
}
Як бачите, цей клас події не містить жодної логіки. Це контейнер для екземпляра App\Models\Order, який було придбано. Трейт SerializesModels, що використовується подією, коректно серіалізує будь-які моделі Eloquent, якщо обʼєкт події серіалізується функцією PHP serialize — наприклад, при використанні слухачів у черзі.
Визначення слухачів
Далі погляньмо на слухача для нашої прикладної події. Слухачі подій отримують екземпляри подій у своєму методі handle. Artisan-команда make:listener, викликана з опцією --event, автоматично імпортує потрібний клас події й вкаже тип події в методі handle. У методі handle ви можете виконувати будь-які дії, необхідні для реагування на подію:
<?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...
}
}
Примітка. Ваші слухачі подій також можуть вказувати типи будь-яких потрібних їм залежностей у своїх конструкторах. Усі слухачі подій розвʼязуються через контейнер сервісів Laravel, тож залежності буде впроваджено автоматично.
Зупинення поширення події
Іноді вам може знадобитися зупинити поширення події до інших слухачів. Ви можете зробити це, повернувши false з методу handle вашого слухача.
Слухачі подій у черзі
Постановка слухачів у чергу може бути корисною, якщо ваш слухач виконує повільне завдання — наприклад, надсилання листа або HTTP-запит. Перш ніж використовувати слухачів у черзі, обовʼязково налаштуйте чергу і запустіть обробник черги на своєму сервері або в локальному середовищі розробки.
Щоб вказати, що слухач має ставитися в чергу, додайте до класу слухача інтерфейс ShouldQueue. Слухачі, згенеровані Artisan-командами make:listener, уже мають цей інтерфейс, імпортований у поточний простір імен, тож ви можете одразу його використовувати:
<?php
namespace App\Listeners;
use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendShipmentNotification implements ShouldQueue
{
// ...
}
Ось і все! Тепер, коли буде відправлено подію, яку обробляє цей слухач, диспетчер подій автоматично поставить слухача в чергу, використовуючи систему черг Laravel. Якщо під час виконання слухача чергою не буде згенеровано винятків, завдання (job) у черзі буде автоматично видалено після завершення обробки.
Налаштування зʼєднання черги, її імені та затримки
Якщо ви хочете налаштувати зʼєднання черги, імʼя черги або час затримки для слухача події, ви можете використати атрибути Connection, Queue і Delay у класі слухача:
<?php
namespace App\Listeners;
use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\Attributes\Connection;
use Illuminate\Queue\Attributes\Delay;
use Illuminate\Queue\Attributes\Queue;
#[Connection('sqs')]
#[Queue('listeners')]
#[Delay(60)]
class SendShipmentNotification implements ShouldQueue
{
// ...
}
Якщо ви хочете визначати зʼєднання черги слухача, імʼя черги або затримку під час виконання, ви можете визначити в слухачі методи viaConnection, viaQueue або withDelay:
/**
* 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;
}
Умовна постановка слухачів у чергу
Іноді вам може знадобитися визначити, чи слід ставити слухача в чергу, на основі даних, доступних лише під час виконання. Щоб цього досягти, до слухача можна додати метод shouldQueue, який визначає, чи слід ставити слухача в чергу. Якщо метод shouldQueue повертає false, слухача не буде поставлено в чергу:
<?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;
}
}
Ручна взаємодія з чергою
Якщо вам потрібен ручний доступ до методів delete і release завдання в черзі, що лежить в основі слухача, ви можете скористатися трейтом Illuminate\Queue\InteractsWithQueue. Цей трейт імпортується за замовчуванням у згенерованих слухачів і надає доступ до цих методів:
<?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 ($condition) {
$this->release(30);
}
}
}
Слухачі подій у черзі та транзакції бази даних
Коли слухачі в черзі відправляються всередині транзакцій бази даних, черга може обробити їх до того, як транзакцію буде зафіксовано. Коли це стається, будь-які зміни, які ви внесли до моделей або записів бази даних під час транзакції, можуть ще не відображатися в базі даних. Крім того, будь-які моделі чи записи бази даних, створені всередині транзакції, можуть не існувати в базі даних. Якщо ваш слухач залежить від цих моделей, під час обробки завдання, яке відправляє слухача в чергу, можуть виникнути несподівані помилки.
Якщо опцію конфігурації after_commit вашого зʼєднання черги встановлено в false, ви все одно можете вказати, що конкретного слухача в черзі має бути відправлено після фіксації всіх відкритих транзакцій бази даних, реалізувавши інтерфейс ShouldQueueAfterCommit у класі слухача:
<?php
namespace App\Listeners;
use Illuminate\Contracts\Queue\ShouldQueueAfterCommit;
use Illuminate\Queue\InteractsWithQueue;
class SendShipmentNotification implements ShouldQueueAfterCommit
{
use InteractsWithQueue;
}
Примітка. Щоб дізнатися більше про обхід цих проблем, перегляньте документацію щодо завдань у черзі та транзакцій бази даних.
Middleware для слухачів у черзі
Слухачі в черзі також можуть використовувати middleware завдань. Middleware завдань дозволяють обгорнути власною логікою виконання слухачів у черзі, зменшуючи кількість шаблонного коду в самих слухачах. Після створення middleware завдання його можна приєднати до слухача, повернувши його з методу middleware слухача:
<?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];
}
}
Зашифровані слухачі в черзі
Laravel дозволяє забезпечити приватність і цілісність даних слухача в черзі за допомогою шифрування. Щоб почати, просто додайте до класу слухача інтерфейс ShouldBeEncrypted. Щойно цей інтерфейс буде додано до класу, Laravel автоматично зашифрує вашого слухача перед тим, як відправити його в чергу:
<?php
namespace App\Listeners;
use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendShipmentNotification implements ShouldQueue, ShouldBeEncrypted
{
// ...
}
Унікальні слухачі подій
Попередження. Унікальні слухачі потребують драйвера кешу, який підтримує блокування. Наразі атомарні блокування підтримують драйвери кешу
memcached,redis,dynamodb,database,fileіarray.
Іноді вам може знадобитися гарантувати, що в черзі в будь-який момент часу перебуває лише один екземпляр конкретного слухача. Ви можете зробити це, реалізувавши інтерфейс ShouldBeUnique у класі слухача:
<?php
namespace App\Listeners;
use App\Events\LicenseSaved;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
class AcquireProductKey implements ShouldQueue, ShouldBeUnique
{
public function __invoke(LicenseSaved $event): void
{
// ...
}
}
У наведеному вище прикладі слухач AcquireProductKey є унікальним. Тож слухача не буде поставлено в чергу, якщо інший екземпляр цього слухача вже перебуває в черзі й не завершив обробку. Це гарантує, що для кожної ліцензії буде отримано лише один ключ продукту, навіть якщо ліцензію буде збережено кілька разів поспіль за короткий час.
У певних випадках вам може знадобитися визначити конкретний «ключ», який робить слухача унікальним, або вказати тайм-аут, після якого слухач більше не залишається унікальним. Щоб цього досягти, ви можете визначити властивості або методи uniqueId і uniqueFor у класі слухача. Методи отримують екземпляр події, що дозволяє використовувати дані події для побудови значення, яке повертається:
<?php
namespace App\Listeners;
use App\Events\LicenseSaved;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
class AcquireProductKey implements ShouldQueue, ShouldBeUnique
{
/**
* The number of seconds after which the listener's unique lock will be released.
*
* @var int
*/
public $uniqueFor = 3600;
public function __invoke(LicenseSaved $event): void
{
// ...
}
/**
* Get the unique ID for the listener.
*/
public function uniqueId(LicenseSaved $event): string
{
return 'listener:'.$event->license->id;
}
}
У наведеному вище прикладі слухач AcquireProductKey є унікальним за ID ліцензії. Тож будь-які нові відправлення слухача для тієї самої ліцензії ігноруватимуться, доки наявний слухач не завершить обробку. Це запобігає отриманню дублікатів ключів продукту для однієї й тієї самої ліцензії. Крім того, якщо наявний слухач не буде оброблено протягом однієї години, блокування унікальності буде звільнено, і в чергу можна буде поставити іншого слухача з тим самим унікальним ключем.
Попередження. Якщо ваш застосунок відправляє події з кількох вебсерверів або контейнерів, вам слід переконатися, що всі ваші сервери взаємодіють з одним і тим самим центральним сервером кешу, щоб Laravel міг точно визначити, чи є слухач унікальним.
Збереження унікальності слухачів до початку обробки
За замовчуванням унікальні слухачі «розблоковуються» після того, як слухач завершує обробку або вичерпує всі спроби повтору. Проте можуть бути ситуації, коли ви хочете, щоб ваш слухач розблоковувався безпосередньо перед обробкою. Щоб цього досягти, ваш слухач має реалізувати контракт ShouldBeUniqueUntilProcessing замість контракту ShouldBeUnique:
<?php
namespace App\Listeners;
use App\Events\LicenseSaved;
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
use Illuminate\Contracts\Queue\ShouldQueue;
class AcquireProductKey implements ShouldQueue, ShouldBeUniqueUntilProcessing
{
// ...
}
Блокування унікальних слухачів
Під капотом, коли відправляється слухач ShouldBeUnique, Laravel намагається отримати блокування з ключем uniqueId. Якщо блокування вже утримується, слухача не буде відправлено. Це блокування звільняється, коли слухач завершує обробку або вичерпує всі спроби повтору. За замовчуванням Laravel використовує для отримання цього блокування типовий драйвер кешу. Проте, якщо ви хочете використовувати інший драйвер для отримання блокування, ви можете визначити метод uniqueVia, який повертає драйвер кешу, що слід використовувати:
<?php
namespace App\Listeners;
use App\Events\LicenseSaved;
use Illuminate\Contracts\Cache\Repository;
use Illuminate\Support\Facades\Cache;
class AcquireProductKey implements ShouldQueue, ShouldBeUnique
{
// ...
/**
* Get the cache driver for the unique listener lock.
*/
public function uniqueVia(LicenseSaved $event): Repository
{
return Cache::driver('redis');
}
}
Примітка. Якщо вам потрібно лише обмежити одночасну обробку слухача, використовуйте натомість middleware завдань WithoutOverlapping.
Слухачі подій із debounce
Іноді вам може знадобитися обробити лише останній екземпляр події, яку багаторазово відправляють за короткий проміжок часу. Ви можете зробити це, додавши атрибут DebounceFor до слухача в черзі:
<?php
namespace App\Listeners;
use App\Events\ProductUpdated;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\Attributes\DebounceFor;
#[DebounceFor(30)]
class UpdateProductSearchIndex implements ShouldQueue
{
/**
* Handle the event.
*/
public function handle(ProductUpdated $event): void
{
// Update the product's search index...
}
/**
* Get the debounce ID for the listener.
*/
public function debounceId(ProductUpdated $event): string
{
return (string) $event->product->getKey();
}
}
У наведеному вище прикладі багаторазове відправлення подій ProductUpdated для того самого продукту протягом 30 секунд застосує debounce до слухача так, що буде оброблено лише останню подію. Різні debounce ID обробляються незалежно.
Якщо ви хочете обмежити, як довго часто відправлювана подія може відкладати слухача, ви можете передати аргумент maxWait до атрибута DebounceFor:
#[DebounceFor(30, maxWait: 120)]
class UpdateProductSearchIndex implements ShouldQueue
{
// ...
}
Ви можете налаштувати сховище кешу, яке використовується для відстеження debounce, визначивши метод debounceVia у своєму слухачі. Метод отримує екземпляр події й має повертати репозиторій кешу:
use Illuminate\Contracts\Cache\Repository;
use Illuminate\Support\Facades\Cache;
public function debounceVia(ProductUpdated $event): Repository
{
return Cache::driver('redis');
}
Слухачі з debounce та унікальні слухачі взаємно виключають одне одного. Слухач, що використовує атрибут DebounceFor, не повинен реалізовувати ShouldBeUnique.
Попередження. Якщо ваш застосунок відправляє події з кількох вебсерверів або контейнерів, вам слід переконатися, що всі ваші сервери взаємодіють з одним і тим самим центральним сервером кешу.
Обробка невдалих завдань
Іноді ваші слухачі подій у черзі можуть завершуватися збоєм. Якщо слухач у черзі перевищує максимальну кількість спроб, визначену вашим обробником черги, у слухачі буде викликано метод failed. Метод failed отримує екземпляр події та 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
{
// ...
}
}
Зазначення максимальної кількості спроб для слухача в черзі
Якщо один із ваших слухачів у черзі стикається з помилкою, ви, найімовірніше, не хочете, щоб він повторював спроби нескінченно. Тому Laravel надає різні способи вказати, скільки разів або протягом якого часу можна намагатися виконати слухача.
Ви можете використати атрибут Tries у класі слухача, щоб указати, скільки разів можна намагатися виконати слухача, перш ніж він вважатиметься невдалим:
<?php
namespace App\Listeners;
use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\Attributes\Tries;
use Illuminate\Queue\InteractsWithQueue;
#[Tries(5)]
class SendShipmentNotification implements ShouldQueue
{
use InteractsWithQueue;
// ...
}
Як альтернативу визначенню кількості спроб перед збоєм, ви можете визначити час, після якого спроби виконати слухача більше не робитимуться. Це дозволяє намагатися виконати слухача будь-яку кількість разів у межах заданого проміжку часу. Щоб визначити час, після якого спроби припиняються, додайте до класу слухача метод retryUntil. Цей метод має повертати екземпляр DateTimeInterface:
use DateTimeInterface;
/**
* Determine the time at which the listener should timeout.
*/
public function retryUntil(): DateTimeInterface
{
return now()->plus(minutes: 5);
}
Якщо визначено і retryUntil, і tries, Laravel надає перевагу методу retryUntil.
Зазначення backoff для слухача в черзі
Якщо ви хочете налаштувати, скільки секунд Laravel має чекати перед повторною спробою виконати слухача, що зіткнувся з винятком, ви можете використати атрибут Backoff у класі слухача:
<?php
namespace App\Listeners;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\Attributes\Backoff;
#[Backoff(3)]
class SendShipmentNotification implements ShouldQueue
{
// ...
}
Якщо для визначення часу backoff слухача вам потрібна складніша логіка, ви можете визначити метод backoff у класі слухача:
/**
* Calculate the number of seconds to wait before retrying the queued listener.
*/
public function backoff(OrderShipped $event): int
{
return 3;
}
Ви можете легко налаштувати «експоненційні» backoff-затримки, повернувши масив значень backoff із методу backoff. У цьому прикладі затримка перед повтором становитиме 1 секунду для першої спроби, 5 секунд для другої, 10 секунд для третьої та 10 секунд для кожної наступної спроби, якщо спроби ще залишилися:
/**
* 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];
}
Зазначення максимальної кількості винятків для слухача в черзі
Іноді ви можете захотіти вказати, що слухача в черзі можна намагатися виконати багато разів, але він має завершитися збоєм, якщо повтори спричинені певною кількістю необроблених винятків (на відміну від звільнення методом release напряму). Щоб цього досягти, ви можете використати атрибути Tries і MaxExceptions у класі слухача:
<?php
namespace App\Listeners;
use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\Attributes\MaxExceptions;
use Illuminate\Queue\Attributes\Tries;
use Illuminate\Queue\InteractsWithQueue;
#[Tries(25)]
#[MaxExceptions(3)]
class SendShipmentNotification implements ShouldQueue
{
use InteractsWithQueue;
/**
* Handle the event.
*/
public function handle(OrderShipped $event): void
{
// Process the event...
}
}
У цьому прикладі слухача буде повторено до 25 разів. Проте слухач завершиться збоєм, якщо він згенерує три необроблені винятки.
Зазначення тайм-ауту для слухача в черзі
Часто ви приблизно знаєте, скільки часу мають виконуватися ваші слухачі в черзі. З цієї причини Laravel дозволяє вказати значення «timeout». Якщо слухач обробляється довше, ніж кількість секунд, зазначена у значенні тайм-ауту, обробник, що виконує слухача, завершиться з помилкою. Ви можете визначити максимальну кількість секунд, протягом яких слухачеві дозволено виконуватися, за допомогою атрибута Timeout у класі слухача:
<?php
namespace App\Listeners;
use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\Attributes\Timeout;
#[Timeout(120)]
class SendShipmentNotification implements ShouldQueue
{
// ...
}
Якщо ви хочете вказати, що слухача слід позначати як невдалого при тайм-ауті, ви можете використати атрибут FailOnTimeout у класі слухача:
<?php
namespace App\Listeners;
use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\Attributes\FailOnTimeout;
#[FailOnTimeout]
class SendShipmentNotification implements ShouldQueue
{
// ...
}
Відправлення подій
Щоб відправити подію, ви можете викликати статичний метод dispatch на події. Цей метод стає доступним для події завдяки трейту Illuminate\Foundation\Events\Dispatchable. Будь-які аргументи, передані в метод dispatch, буде передано в конструктор події:
<?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');
}
}
Якщо ви хочете відправляти подію умовно, ви можете скористатися методами dispatchIf і dispatchUnless:
OrderShipped::dispatchIf($condition, $order);
OrderShipped::dispatchUnless($condition, $order);
Примітка. Під час тестування може бути корисно перевіряти, що певні події було відправлено, не запускаючи насправді їхніх слухачів. Вбудовані помічники для тестування Laravel роблять це елементарним.
Відправлення подій після транзакцій бази даних
Іноді ви можете захотіти вказати Laravel відправляти подію лише після фіксації активної транзакції бази даних. Для цього ви можете реалізувати інтерфейс ShouldDispatchAfterCommit у класі події.
Цей інтерфейс вказує Laravel не відправляти подію, доки поточну транзакцію бази даних не буде зафіксовано. Якщо транзакція завершиться невдало, подію буде відкинуто. Якщо на момент відправлення події жодна транзакція бази даних не виконується, подію буде відправлено негайно:
<?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,
) {}
}
Відкладення подій
Відкладені події дозволяють затримати відправлення подій моделей і виконання слухачів подій до завершення певного блоку коду. Це особливо корисно, коли вам потрібно переконатися, що всі повʼязані записи створено до того, як спрацюють слухачі подій.
Щоб відкласти події, передайте замикання в метод Event::defer():
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!']);
});
Усі події, спричинені всередині замикання, буде відправлено після виконання замикання. Це гарантує, що слухачі подій матимуть доступ до всіх повʼязаних записів, створених під час відкладеного виконання. Якщо всередині замикання станеться виняток, відкладені події не буде відправлено.
Щоб відкласти лише певні події, передайте масив подій другим аргументом у метод defer:
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]);
Підписники подій
Написання підписників подій
Підписники подій — це класи, які можуть підписуватися на кілька подій зсередини самого класу підписника, що дозволяє визначити кілька обробників подій в одному класі. Підписники мають визначати метод subscribe, який отримує екземпляр диспетчера подій. Ви можете викликати метод listen на переданому диспетчері, щоб зареєструвати слухачів подій:
<?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']
);
}
}
Якщо методи-слухачі подій визначено всередині самого підписника, вам може бути зручніше повертати масив подій та імен методів із методу subscribe підписника. Laravel автоматично визначить імʼя класу підписника під час реєстрації слухачів подій:
<?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',
];
}
}
Реєстрація підписників подій
Після написання підписника Laravel автоматично зареєструє методи-обробники всередині підписника, якщо вони відповідають конвенціям виявлення подій Laravel. В іншому разі ви можете вручну зареєструвати свого підписника за допомогою методу subscribe фасаду Event. Зазвичай це слід робити в методі boot вашого AppServiceProvider:
<?php
namespace App\Providers;
use App\Listeners\UserEventSubscriber;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Event::subscribe(UserEventSubscriber::class);
}
}
Тестування
Тестуючи код, який відправляє події, ви можете захотіти вказати Laravel не виконувати слухачів події насправді, оскільки код слухача можна протестувати безпосередньо й окремо від коду, який відправляє відповідну подію. Звісно, щоб протестувати самого слухача, ви можете створити екземпляр слухача й викликати метод handle напряму у своєму тесті.
За допомогою методу fake фасаду Event ви можете запобігти виконанню слухачів, виконати код, що тестується, а потім перевірити, які події відправив ваш застосунок, використовуючи методи assertDispatched, assertNotDispatched і assertNothingDispatched:
<?php
use App\Events\OrderFailedToShip;
use App\Events\OrderShipped;
use Illuminate\Support\Facades\Event;
test('orders can be shipped', function () {
Event::fake();
// Виконуємо відправлення замовлення...
// Перевіряємо, що подію було відправлено...
Event::assertDispatched(OrderShipped::class);
// Перевіряємо, що подію було відправлено двічі...
Event::assertDispatched(OrderShipped::class, 2);
// Перевіряємо, що подію було відправлено один раз...
Event::assertDispatchedOnce(OrderShipped::class);
// Перевіряємо, що подію не було відправлено...
Event::assertNotDispatched(OrderFailedToShip::class);
// Перевіряємо, що жодної події не було відправлено...
Event::assertNothingDispatched();
});
<?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();
// Виконуємо відправлення замовлення...
// Перевіряємо, що подію було відправлено...
Event::assertDispatched(OrderShipped::class);
// Перевіряємо, що подію було відправлено двічі...
Event::assertDispatched(OrderShipped::class, 2);
// Перевіряємо, що подію було відправлено один раз...
Event::assertDispatchedOnce(OrderShipped::class);
// Перевіряємо, що подію не було відправлено...
Event::assertNotDispatched(OrderFailedToShip::class);
// Перевіряємо, що жодної події не було відправлено...
Event::assertNothingDispatched();
}
}
Ви можете передати замикання в методи assertDispatched або assertNotDispatched, щоб перевірити, що було відправлено подію, яка проходить заданий «тест на істинність». Якщо було відправлено хоча б одну подію, яка проходить заданий тест на істинність, перевірка буде успішною:
Event::assertDispatched(function (OrderShipped $event) use ($order) {
return $event->order->id === $order->id;
});
Якщо ви просто хочете перевірити, що слухач події слухає задану подію, ви можете скористатися методом assertListening:
Event::assertListening(
OrderShipped::class,
SendShipmentNotification::class
);
Попередження. Після виклику
Event::fake()жодного слухача подій не буде виконано. Тож якщо ваші тести використовують фабрики моделей, які покладаються на події — наприклад, створення UUID під час подіїcreatingмоделі, — вам слід викликатиEvent::fake()після використання фабрик.
Підміна частини подій
Якщо ви хочете підмінити слухачів подій лише для певного набору подій, ви можете передати їх у метод fake або fakeFor:
test('orders can be processed', function () {
Event::fake([
OrderCreated::class,
]);
$order = Order::factory()->create();
Event::assertDispatched(OrderCreated::class);
// Інші події відправляються як зазвичай...
$order->update([
// ...
]);
});
/**
* Test order process.
*/
public function test_orders_can_be_processed(): void
{
Event::fake([
OrderCreated::class,
]);
$order = Order::factory()->create();
Event::assertDispatched(OrderCreated::class);
// Інші події відправляються як зазвичай...
$order->update([
// ...
]);
}
Ви можете підмінити всі події, крім заданого набору подій, за допомогою методу except:
Event::fake()->except([
OrderCreated::class,
]);
Обмежені за областю підміни подій
Якщо ви хочете підмінити слухачів подій лише для частини свого тесту, ви можете скористатися методом fakeFor:
<?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;
});
// Події відправляються як зазвичай, і спостерігачі виконуватимуться...
$order->update([
// ...
]);
});
<?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;
});
// Події відправляються як зазвичай, і спостерігачі виконуватимуться...
$order->update([
// ...
]);
}
}
Виправити терміни, дописати розділ або взяти нову главу може кожен. Термінологію узгоджуємо в глосарії, щоб переклад лишався однорідним.