Управление событиями в Laravel

В этой статье мы попробуем разобраться как запускать события и как их обрабатывать. Напомню, что событие — это своего рода сообщение, возникающее в определенный момент времени. Некоторые события уже определены в Laravel, но в основном эта задача ложится на плечи разработчика.

И так, у нас есть сообщение. Оно, как правило, содержит определённую информацию. И чтобы получить эту информацию, нужно создать обработчик, который будет «слушать» это событие и, когда событие произойдёт, выполнит указанные в нём действия.

Весь функционал для работы с событиями находится в специальном классе Illuminate\Events\Dispatcher. И мы будем использовать 2 его метода — fire() для запуска события и listen() для регистрации обработчиков. Метод fire() является всего лишь синонимом метода dispatch(). Напомню, что мы говорим о Laravel версии 5.4.

Регистрация событий и обработчиков (listeners)

Через EventServiceProvider

Это самый простой вариант. EventServiceProvider — это специальный системный сервис-провайдер, который предназначен конкретно для событий. Он содержит 2 закрытых свойства — $listen и $subscribe. Первое предназначено для регистрации событий и обработчиков. Второе — для подписчиков (что это мы разберём ниже).

// App\Providers\EventServiceProvider
protected $listen = [
    'App\Events\myEvent' => [
        'App\Listeners\myEventListener',
    ],
];

Ключи массива — это названия событий, а значения — это обработчики. Обработчиков для одного события может быть несколько. Laravel автоматически зарегистрирует указанные в данном свойстве обработчики для каждого события. В качестве названия события можно указать полное квалификационное имя класса события или просто строку с любым названием. В качестве обработчика события указывается класс, который должен содержать метод handle. Этот метод будет вызван при срабатывании события. Но Laravel позволяет указать свой метод. Для этого его нужно указать через знак @ — App\Listeners\myEventListener@myMethod.

В Laravel принято классы событий размещать в папке App\Events, а файлы обработчиков в папке App\Listeners.

Классы можно создать вручную, а можно выполнить команду php artisan event:generate — Laravel сам создаст файлы классов, указанные с свойстве $listen, и положит их в соответствующие папки.

Ручная регистрация

При данном способе регистрации нам понадобится диспетчер событий Illuminate\Events\Dispatcher и его метод listen. Обратиться к диспетчеру можно через фасад Event или функцию-хелпер app('events'). Данный способ регистрации позволяет указать в качестве обработчика анонимную функцию или класс обработчика

// EventServiceProvider
public function boot()
{
    parent::boot();
    # Через фасад (событие - название класса). Класс нужно создать самостоятельно.
    Event::listen('App\Events\myEventClass', function ($eventObject) {
        //
    });
    # Через функцию (событие - строка)
    app('events')->listen('myEvent', function ($foo, $bar) {
        //
    });
    # Вместо анонимной функции указываем класс
    app('events')->listen('myEvent', 'App\Listeners\myListener'); // В нём должен быть метод handle
    # Указываем собственный метод класса
    app('events')->listen('myEvent', 'App\Listeners\myListener@custom');
}
}

И фасад и функция доступны в любом месте приложения, поэтому вызывать их можно и в специальном сервис-провайдере EventServiceProvider, и в своём сервис-провайдере своего пакета, и в контроллерах, и в сервисах, и даже в файле маршрутов. Главное, зарегистрировать раньше вызова события.

Можно зарегистрировать несколько событий для одного обработчика. Для этого их нужно указать в массиве.

Event::listen(['myEvent1','myEvent2'], function ($eventName, $data) {
    //
});

Вместо указания нескольких событий можно использовать звёздочку * (так называемый wildcard).

Event::listen('Email.*', function ($eventName, $data) {
    // Сработает на события Email.init, Email.sending, Email.sent
});

Важно знать!

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

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

Если вам нужно остановить дальнейшее выполнение событий (типа event.stopPropagation() в javascript), обработчик должен вернуть false.

Инициирование события

Для запуска события можно использовать фасад Event::fire() или функцию-хелпер event().

/**
 * Запускает событие и вызывает обработчики.
 *
 * @param  string|object  $event Название события
 * @param  mixed  $payload Данные
 * @param  bool  $halt Режим вызова обработчиков
 * @return array|null
 */
public function fire($event, $payload = [], $halt = false)

Место и условия запуска определяется программистом согласно логике приложения. В качестве агрумента можно указать или объект класса события или название события. В зависимости от этого логика метода несколько отличается.

Если указать объект события, то Laravel трансформирует вызов следующим образом:

  1. В качестве первого параметра будет указано название класса объекта. Это потому, что в качестве ключей массива событий используется строковое значение (см. первый способ регистрации событий).
  2. А во второй параметр будет передан объект события. Поэтому, если вы указали свои данные во втором параметре, они будут перезаписаны. Передавать данные нужно в конструктор при создании объекта события.
# Запуск события с передачей объекта события
$response = event(new App\Events\myEventClass($data));
# В зарегистрированном обработчике ловим объект события
...
public function handle(App\Events\myEventClass $event)
{
    // 
}

Второй вариант запуска событий — указать строковое название события.

# Запуск события по названию
$response = event('myEvent', $data)); // Для передачи несколько значений используйте массив.

Передавать нужно столько данных, столько определено в обработчике.

// Регистрируем обработчик для нескольких событий
Event::listen(['myEvent1','myEvent2'], function ($eventName, $data) {
    if ($eventName == 'myEvent1') {
    //
    } else {
    //
    }
});
...
$response = event('myEvent1', ['myEvent1', array('Данные')]));

Третий параметр метода fire() включает режим, при котором обработчики выполняются до первого, который вернёт какое-нибудь значение. После этого обработка события прекращается и метод возвращает это значение.

В обычном режиме ($halt = false) метод возвращает или null или массив значений, которые возвращают обработчики.

Подписчики (subscribers)

Теперь мы знаем, что слушатели (listeners) — это обработчики события. Давайте разберёмся, что такое подписчики. А подписчики — это всего лишь классы, которые содержат всю логику регистрации и обработки событий.

<?php
namespace App\Listeners;
class UserEventSubscriber
{
    /**
     * Обработчик события "login".
     */
    public function onUserLogin($event) {}
    /**
     * Обработчик события "logout".
     */
    public function onUserLogout($event) {}
    /**
     * Регистрация обработчиков для событий логина и логаута.
     *
     * @param  Illuminate\Events\Dispatcher  $events
     */
    public function subscribe($events)
    {
        $events->listen(
            'Illuminate\Auth\Events\Login',
            'App\Listeners\UserEventSubscriber@onUserLogin'
        );
        $events->listen(
            'Illuminate\Auth\Events\Logout',
            'App\Listeners\UserEventSubscriber@onUserLogout'
        );
    }
}

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

    • Указать класс в специальном свойстве $subscribers сервис-провайдера EventServiceProvider
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider
{
    protected $listen = [];
    /**
     * The subscriber classes to register.
     *
     * @var array
     */
    protected $subscribe = [
        'App\Listeners\UserEventSubscriber',
    ];
}
    • Самостоятельно зарегистрировать в нужном месте, например, в сервис-провайдере своего пакета в методе boot()
public function boot() {
    Event::subscribe('App\Listeners\UserEventSubscriber');
}

Заключение

Конечно, я рассмотрел не все возможности функционала событий, но данных знаний хватит для большинства проектов. В дальнейшем вы можете самостоятельно освоить, например, очереди событий. А в следующей статье мы рассмотрим события моделей — их специфику и применение.

П.С. Официальная документация по событиям.