События
Событие — сообщение «сейчас произошло то-то», на которое может отозваться любой код. Обработка запроса в Laminas — цепочка таких событий: определить маршрут, вызвать контроллер, вывести шаблон, отправить ответ. К каждому шагу можно подключить слушателя — функцию, которая выполнится в этот момент.
Этапы запроса
| Событие | Когда происходит |
|---|---|
MvcEvent::EVENT_BOOTSTRAP | Приложение собрано |
MvcEvent::EVENT_ROUTE | Определяется маршрут |
MvcEvent::EVENT_DISPATCH | Вызывается контроллер |
MvcEvent::EVENT_DISPATCH_ERROR | Ошибка: нет маршрута или исключение |
MvcEvent::EVENT_RENDER | Выводится шаблон |
MvcEvent::EVENT_FINISH | Ответ готов к отправке |
Первый слушатель
Слушателей подключают в методе onBootstrap() класса модуля — файл module/Blog/src/Module.php. Laminas вызывает этот метод сам при каждом запросе:
// module/Blog/src/Module.php
use Laminas\Mvc\MvcEvent;
public function onBootstrap(MvcEvent $event): void
{
$events = $event->getApplication()->getEventManager();
$events->attach(MvcEvent::EVENT_FINISH, function (MvcEvent $e) {
$e->getResponse()->getHeaders()->addHeaderLine('X-Frame-Options', 'DENY');
});
}Метод attach() принимает имя события и функцию. Теперь к каждому ответу сайта добавляется заголовок X-Frame-Options: его видно в инструментах разработчика браузера на вкладке сети.
Что доступно слушателю
Слушатель получает объект MvcEvent с данными запроса:
| Метод | Что возвращает |
|---|---|
getRequest(), getResponse() | Запрос и ответ |
getRouteMatch() | Сработавший маршрут и его параметры |
getApplication()->getServiceManager() | Контейнер сервисов |
getParam('exception') | Исключение (в событии ошибки) |
Приоритет
Третий аргумент attach() — приоритет. Чем он больше, тем раньше вызывается слушатель; по умолчанию он равен 1.
$events->attach(MvcEvent::EVENT_DISPATCH_ERROR, function (MvcEvent $e) {
$exception = $e->getParam('exception');
if ($exception !== null) {
error_log($exception->getMessage());
}
}, 100);Слушатель с приоритетом 100 запишет текст исключения в журнал PHP раньше, чем встроенный слушатель покажет страницу ошибки.
Свои события
Сервис может сам сообщать о том, что произошло, не зная, кто отреагирует. Создайте сервис, который сохраняет статью и вызывает событие:
// module/Blog/src/Service/PostService.php
namespace Blog\Service;
use Blog\Model\Post;
use Blog\Model\PostTable;
use Laminas\EventManager\EventManagerInterface;
class PostService
{
public function __construct(
private PostTable $posts,
private EventManagerInterface $events,
) {}
public function create(Post $post): void
{
$this->posts->save($post);
$this->events->trigger('post.created', $this, ['post' => $post]);
}
}Фабрика выдаёт сервису собственный менеджер событий и помечает его именем класса:
// module/Blog/src/Service/PostServiceFactory.php
public function __invoke(ContainerInterface $container): PostService
{
$events = $container->get('EventManager');
$events->setIdentifiers([PostService::class]);
return new PostService($container->get(PostTable::class), $events);
}Подписываются на такое событие через общий менеджер — в onBootstrap():
$shared = $events->getSharedManager();
$shared->attach(PostService::class, 'post.created', function ($e) {
error_log('Новая статья: ' . $e->getParam('post')->title);
});Первый аргумент — метка источника, второй — имя события. Если в действии add вызвать $this->postService->create($post) вместо $this->posts->save($post), после каждой новой статьи в журнале появится строка. Отправку писем или сброс кеша добавляют таким же слушателем, не меняя сам сервис.
Частая ошибка
Слушатели из onBootstrap() выполняются при каждом запросе и для всех страниц сайта. Запрос к базе или чтение файлов внутри них замедляют всё приложение. Держите слушателей короткими, а сервисы получайте из контейнера внутри функции-слушателя: тогда они создадутся, только когда действительно понадобятся.