События

Содержание:

Введение

События Ларавел обеспечивают простую реализацию наблюдателя, позволяя вам подписаться и прослушивать различные события, которые происходят в вашем приложении. Классы событий обычно хранятся в папке app/Events, в то время как их слушатели располагаются в app/Listeners. Не стоит беспокоиться, если вы не видите эти директории в вашем приложении, потому что они будут созданы при генерации событий и слушателей во время использования команд Artisan.

События выступают с качестве отличного способо разделить разнообразные аспекты вашего приложения, в силу того, что одиночное событие может иметь несколько слушателей, которые не зависят друг от друга. Например, вы можете захотеть отправить уведомление Slack вашему пользователю какждый раз, когда появляется заявка. Вместо соединения вашего кода обработки заказа с уведомлениями Slack, вы можете запустить событие OrderShipped, которое слушатель может получить и трансформировать в уведомление Slack.


Регистрация событий и слушателей

EventServiceProvider, кключённый в базовый фреймворк Ларавел предоставляет удобное место для регистрации всех слушателей вашего приложения. Свойство listen содержит массив элементов (ключей) и их слушателей (знечений). Вы можете добавить так много событий в этот массив, как это требуется для вашего приложения. Например, давайте добавим событие OrderShipped:

/**
 * The event listener mappings for the application.
 *
 * @var array
 */
protected $listen = [
    'App\Events\OrderShipped' => [
        'App\Listeners\SendShipmentNotification',
    ],
];

Генерация событий и слушателей

Конечно же, это очень обременительно создавать файлы и слушателя для каждого событияи. Вместо этого необходимо добавить события в ваш EventServiceProvider и использовать команду event:generate. Эта команда создаст любые события или слушатели, которые перечислены в вашем файле EventServiceProvider. События и слушатели, которые уже существую остануться нетронутыми:

php artisan event:generate

Ручная регистрация событий

Обычно, события должны регистрироваться через EventServiceProvider и массив $listen; Однако, вы можете также регистрировать события основанные на замыкании в методе boot вашего EventServiceProvider:

/**
 * Register any other events for your application.
 *
 * @return void
 */
public function boot()
{
    parent::boot();

    Event::listen('event.name', function ($foo, $bar) {
        //
    });
}

Шаблон слушателей события

Вы даже можете зарегистрировать слушателей используя * как параметр шаблона, что позволит вам поймать несколько событий для одного и того же слушателя. Шаблон слушателей получают имя события в качестве первого аргумента, и массив данных события в качестве второго:

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

Обнаружение событий

Обнаружение событий доступно для Laravel 5.8.9 и выше. {note} Event Discovery is available for Laravel 5.8.9 or later.

Вместо регистрации событий и слушателей вручную в массиве $listen в EventServiceProvider, вы можете разрешить автоматическое обнаружение событий. Когда обнаружение событий включено, Laravel автоматически найдет и зарегистрирует ваши события и слушателей путём сканирования папки Listeners вашего приложения. Дополнительно, все явные события перечесленные в EventServiceProvider также будут регистрироваться.

Laravel находит слушателей событий путём сканирования классов слушателя. Когда фреймворк находит любой метод из класса слушателя, который начинается с handle, Laravel зарегистрирует эти методы как слушатели событий для события, которое вписано в подпись метода:

use App\Events\PodcastProcessed;

class SendPodcastProcessedNotification
{
    /**
     * Handle the given event.
     *
     * @param  \App\Events\PodcastProcessed
     * @return void
     */
    public function handle(PodcastProcessed $event)
    {
        //
    }
}

По умолчанию, открытие событий отключено, но вы можете включить его путём переписывания метода shouldDiscoverEvents для вашего EventServiceProvider:

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

По умолчанию, все слушатели внутри соответсвующей папки будут просканированы. Если вы хотите определить дополнительную папку для сканирования, вам необходимо переписать метод discoverEventsWithin в вашем EventServiceProvider:

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

На сервере, вам скорее всего не захочется, чтобы фреймворк сканировал всех слушателей для каждого запроса. Следовательно, по время процесса разработки, вам необходимо выполнить команду Artisan event:cache, чтобы закешировать все события и слушателей для вашего приложения. Этот файл (манифест) будет использован фреймворком для ускорения процесса регистрации. Команда event:clear может быть использована для уничтожения кэша.

Команда event:list может быть использована для отображения списка событий и слушателей зарегистрированных в проложении.

Определение событий

Класс события — это контейнер данных, который хранит информацию связанную с событием. Например, давайте предположим, что сгенерированное событие OrderShipped будет получена как объект Eloquent ORM:

<?php

namespace App\Events;

use App\Order;
use Illuminate\Queue\SerializesModels;

class OrderShipped
{
    use SerializesModels;

    public $order;

    /**
     * Create a new event instance.
     *
     * @param  \App\Order  $order
     * @return void
     */
    public function __construct(Order $order)
    {
        $this->order = $order;
    }
}

Как вы можете видеть, этот класс события не содержит логики. Это контейнер для экземпляра Order, который был оформлен. Трейт SerializesModels используемый событием элегантно проведёт сериализацию любых моделей Eloquent, если объект сериализуется с помощью функции PHP serialize.


Определение слушателей

Далее, давайте взглянем на слушателя для нашего примера события. Слушатели события получают экземпляр события в методе handle. Команда event:generate автоматически импортирует нужный класс и вписывает событие в метод handle. Внутри метода handle, вы можете выполнять любые действия необходимые для ответа событию:

<?php

namespace App\Listeners;

use App\Events\OrderShipped;

class SendShipmentNotification
{
    /**
     * Create the event listener.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Handle the event.
     *
     * @param  \App\Events\OrderShipped  $event
     * @return void
     */
    public function handle(OrderShipped $event)
    {
        // Access the order using $event->order...
    }
}
Ваши слушатели событий могут принимать необходимые зависимости через их конструкторы. Все слушатели события разрешаются через сервис контейнер, поэтому внедрение зависимостей проходит автоматически.

Остановка распространения события

Иногда вы можете захотеть остановить распространение события для слушателей. Для этого вам необходимо вернуть false из метода handle вашего слушателя.


Слушатели очереди событий

Очередь слушателй может быть выгодной, если ваш слушатель собирается выполнить медленную задачу, такую как отправка e-mail или выполнение HTTP запроса. Перед тем, как начать работать с очередями слушателей, убедитесь в том, что настроили ваши очереди и запустите очередь слушателя на сервере или локальной среде разработки.

Чтобы указать какой слушатель должен быть помещён в очередь, добавьте интерфейс ShouldQueue в класс слушателя. Слушатели сгенерированные командой Artisan event:generate уже имеют этот интерфейс импортируемый в текущее пространство имен, поэтому вы можете этим пользоваться:

<?php

namespace App\Listeners;

use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;

class SendShipmentNotification implements ShouldQueue
{
    //
}

И это все! Теперь, когда слушатель вызывается для события, он автоматически будет добавлен в очередь диспетчером событий используя систему очередей Laravel. Если не было выкинуто ни одно исключение при выполнении слушателя в очереди, задание очереди будет автоматически удалено после завершения процесса.

Настройка соединения и имени очереди

Если вы хотите настроить соединение очереди, имя очереди или время задержки очереди прослушивателя событий, вы можете определить параметры $connection, $queue, или $delay для класса слушателя:

<?php

namespace App\Listeners;

use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;

class SendShipmentNotification implements ShouldQueue
{
    /**
     * The name of the connection the job should be sent to.
     *
     * @var string|null
     */
    public $connection = 'sqs';

    /**
     * The name of the queue the job should be sent to.
     *
     * @var string|null
     */
    public $queue = 'listeners';

    /**
     * The time (seconds) before the job should be processed.
     *
     * @var int
     */
    public $delay = 60;
}

Добавления слушателя в очередь с условием

Иногда вам может быть необходимо определить, какого слушателя необходимо добавить в очередь основываясь на некоторых данных, которые доступны только "на лету". Для выполнения такой задачи, метод shouldQueue может быть добавлен слушателю, чтобы определить, должен ли слушатель быть помещён в очередь и выполняться синхронно:

<?php

namespace App\Listeners;

use App\Events\OrderPlaced;
use Illuminate\Contracts\Queue\ShouldQueue;

class RewardGiftCard implements ShouldQueue
{
    /**
     * Reward a gift card to the customer.
     *
     * @param  \App\Events\OrderPlaced  $event
     * @return void
     */
    public function handle(OrderPlaced $event)
    {
        //
    }

    /**
     * Determine whether the listener should be queued.
     *
     * @param  \App\Events\OrderPlaced  $event
     * @return bool
     */
    public function shouldQueue(OrderPlaced $event)
    {
        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.
     *
     * @param  \App\Events\OrderShipped  $event
     * @return void
     */
    public function handle(OrderShipped $event)
    {
        if (true) {
            $this->release(30);
        }
    }
}

Обработка невыполненных заданий

Иногда может так случится, что очередь слушателей события потерпела неудачу. Если прослушиватель очереди превышает максимальное число попыток, как определено, метод failed будет вызван для вашего слушателя. Метод failed получает экземпляр события и исключение, которое вызвало неудачу:

<?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.
     *
     * @param  \App\Events\OrderShipped  $event
     * @return void
     */
    public function handle(OrderShipped $event)
    {
        //
    }

    /**
     * Handle a job failure.
     *
     * @param  \App\Events\OrderShipped  $event
     * @param  \Exception  $exception
     * @return void
     */
    public function failed(OrderShipped $event, $exception)
    {
        //
    }
}

Отправка событий

Для отправки события, вам необходимо передать экземпляр события в помощник event. Этот помощник отправит событие по всем его зарегистрированным слушателям. В силу того, что помощник event доступен глобально, вы можете вызвать его из любой точки приложения:

<?php

namespace App\Http\Controllers;

use App\Events\OrderShipped;
use App\Http\Controllers\Controller;
use App\Order;

class OrderController extends Controller
{
    /**
     * Ship the given order.
     *
     * @param  int  $orderId
     * @return Response
     */
    public function ship($orderId)
    {
        $order = Order::findOrFail($orderId);

        // Order shipment logic...

        event(new OrderShipped($order));
    }
}
При тестировании может быть полезным отправлять без фактического запуска слушателей. Фальшивые собития помогут вам релазовать такую возможность.

Подписчики событий

Написание подписчиков событий

Подписчики событий — это классы, которые могут быть подписаны на несколько событий из самого класса. Что позволяет вам определить несколько обработчиков внутри одиночного класса. Подписчики должны определить метод subscribe, которому будет передан экземпляр события. Вы можете вызвать метод listen для данного отправителя, чтобы зарегистрировать слушателей событий:

<?php

namespace App\Listeners;

class UserEventSubscriber
{
    /**
     * Handle user login events.
     */
    public function handleUserLogin($event) {}

    /**
     * Handle user logout events.
     */
    public function handleUserLogout($event) {}

    /**
     * Register the listeners for the subscriber.
     *
     * @param  \Illuminate\Events\Dispatcher  $events
     */
    public function subscribe($events)
    {
        $events->listen(
            'Illuminate\Auth\Events\Login',
            'App\Listeners\UserEventSubscriber@handleUserLogin'
        );

        $events->listen(
            'Illuminate\Auth\Events\Logout',
            'App\Listeners\UserEventSubscriber@handleUserLogout'
        );
    }
}

Регистрация подписчиков событий

После написания подписчика, вы готовы зарегистрировать его с отправителем события. Вы можете зарегистрировать подписчиков используя свойство $subscribe для EventServiceProvider. Например, давайте добавим UserEventSubscriber в список:

<?php

namespace App\Providers;

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 = [
        'App\Listeners\UserEventSubscriber',
    ];
}