События в Laravel

Источник: «Events in Laravel»
События в Laravel — это полезное удобство программирования, позволяющее разработчику оповещать, что в программе произошло нечто значимое.

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

Подумайте об этом так — мама объявляет всему дому: Пора идти!. Предполагая, что дети обратили на это внимание, начинается череда действий: Джек выключает свет, Лорен запирает входную дверь, Энди зовёт собаку со двора, а дети начинают усаживаться в машину.

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

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

Понимание типов событий

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

Нативные события запускаются фреймворком при успешной аутентификации, создании моделей Eloquent, а также в определённые моменты цикла запросов приложения.

Аналогично, сторонние пакеты могут инициировать события, позволяя мне регистрировать слушателей для реагирования на них.

Пользовательские события связаны с уникальной логикой работы приложения, например, TeacherHasCreatedNewClassroom или StudentHasCompletedAnAssignment. Именно эти события я чаще всего использую.

Попробуйте выполнить php artisan event:list, чтобы увидеть все события, зарегистрированные в вашем приложении!

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

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

1. Класс события

Классы событий — это простые PHP-классы с описательным именем. Мне нравится использовать прошедшее время того, что мы описываем, в форме: {Actor}Has{ActionDescription}. Эти классы могут принимать параметры конструктора, но это не обязательно.

class StudentHasJoinedClassroom
{
public function __construct(public Student $student)
{
}
}

2. Слушатели события

Слушатели называются так же, как и события, но в них обычно используются более ориентированные на действия имена, например NotifyStudentOfNewAssignment или WelcomeStudentToNewClassroom. Некоторые добавляют Listener в конец классов слушателей, но мне кажется, что это слишком многословно.

Они имеют единственный метод handle, который принимает экземпляр события, вызвавшего его.

class WelcomeStudentToNewClassroom
{
public function handle(StudentHasJoinedClassroom $event): void
{
// Сделайте что-нибудь, чтобы поприветствовать студента
}
}

3. Регистрация события

Это ключевой элемент, который сопоставляет слушателей с конкретными событиями. Самый простой способ реализовать это — использовать EventServiceProvider, который содержит массив имён классов событий и соответствующих им слушателей.

protected $listen = [
StudentHasJoinedClassroom::class => [
WelcomeStudentToNewClassroom::class,
AssignDefaultHomework::class,
],
]

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

Очередь слушателей события

По умолчанию слушатели будут обрабатываться в том порядке, в котором они были зарегистрированы. Это может быть необходимо в зависимости от ситуации, однако одним из преимуществ использования подхода "событие/слушатель" является то, что слушатели могут обрабатываться асинхронно в одной из очередей заданий Laravel. Поскольку количество слушателей, которые может вызвать событие, не ограничено, их последовательный запуск приведёт к замедлению времени отклика. Если слушатели не зависят друг от друга, мы можем поместить их в очередь для обработки при наличии свободных воркеров.

Это достигается путём добавления к слушателю интерфейса Illuminate\Contracts\Queue\ShouldQueue.

use Illuminate\Contracts\Queue\ShouldQueue;

class QueuedEventListenerExample implements ShouldQueue
{
public function handle($event): void
{
//
}
}

Если предположить, что ваша очередь обрабатывает задания, то теперь этот слушатель будет вынесен за пределы основного выполнения, что позволит приложению быстрее получать ответы.

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

Если слушатели работают синхронно, то возврат false из метода handle одного из слушателей приведёт к остановке цепочки.

События модели

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

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

Мы можем вызвать собственное событие TeacherHasBeenDeleted и назначить слушателей для обработки дополнительных удалений. Или же мы можем подключиться к собственному событию Deleting и запускать наши слушатели при его возникновении.

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

Действия при событиях модели

Для того чтобы выполнять действия/экшены при наступлении событий модели, мы должны сообщить Laravel, какой код должен быть выполнен.

Обратные вызовы загрузки

Если код слушателя прост, мы можем вызвать метод boot в модели Eloquent, запускаемый при наступлении события connected. Это выглядит следующим образом:

class Teacher extends Model
{

public static function boot()
{
parent::boot();

static::deleting(function (Model $teacher) {
// инициировать удаление соответствующих ресурсов
});
}
}

Таким образом может быть зарегистрировано любое из событий модели.

Наблюдатели модели

Если у нас много кода в загрузочных хуках, то может оказаться полезным вынести обратные вызовы в отдельный файл. Мы можем создать ещё один класс, называемый "наблюдатель"/"observer".

class TeacherObserver
{
public function deleting(Teacher $teacher): void
{
// инициировать удаление соответствующих ресурсов
}

А затем мы должны зарегистрировать наблюдателя в EventServiceProvider в таком виде:

protected $observers = [
Teacher::class => [TeacherObserver::class],
];

Я предпочитаю не использовать наблюдатели, поскольку о них легко забыть, так как они находятся далеко за кулисами. Просто взглянув на модель Teacher, мы не увидим никаких намёков на то, что с событием удаления что-то происходит. Мы должны знать, что существует зарегистрированный наблюдатель.

$dispatchesEvents

Добавление свойства $dispatchesEvents к нашей модели даёт возможность подключить пользовательский класс событий к нативным событиям модели.

class Teacher extends Model
{
protected $dispatchesEvents = [
'deleting' => TeacherIsBeingDeleted::class,
];
}

После того как мы указали Laravel вызывать событие TeacherIsBeingDeleted при удалении модели Teacher, мы можем использовать массив $listen провайдера EventServiceProvider для запуска слушателей, как и в случае с пользовательским событием.

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

Для организации событий и слушателей модели я предпочитаю использовать подписчиков событий. Этот подход сочетает в себе ясность EventServiceProvider, чистую абстракцию наблюдателей и наглядность $dispatchesEvents.

Начнём с определения пользовательских событий, которые должны запускаться после нативных событий модели. См. пример $dispatchesEvents выше.

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

class TeacherSubscriber
{
public function handleDeletionAssociatedResources(TeacherIsDeleting $event): void
{
// инициировать удаление соответствующих ресурсов
}

public function subscribe(Dispatcher $events): array
{
return [
TeacherIsDeleting::class => 'handleDeletionOfAssociatedResources'
];
}
}

Наконец, подписчик регистрируется в EventServiceProvider, где я обычно смотрю, какие события обрабатываются.

class EventServiceProvider extends ServiceProvider
{
protected $subscribe = [
TeacherSubscriber::class,
];
}

Таким образом, удобно открыть модель Teacher, посмотреть на свойство $dispatchesEvents, чтобы увидеть, на какие события мы реагируем, посетить EventServiceProvider, чтобы увидеть, что обрабатывает события, а затем обратиться к TeacherSubscriber, чтобы получить конкретные данные.

В заключение

Отказ от линейного мышления "шаг 1 → шаг 2 → шаг 3" даёт нам возможность более реалистично проектировать приложения. Поднятие событий в сочетании с очередями заданий означает, что мы можем выбросить событие и продолжить работу с программой, а слушатели событий обрабатывают информацию вне основного потока. Это очень удобный паттерн, который можно иметь в своём распоряжении!

Дополнительные материалы

Предыдущая Статья

Не бойтесь JavaScript-генераторов

Следующая Статья

Laravel Impersonate — Как выдавать себя за других пользователей