Подключить контейнер¶
Слушателю почти всегда что-то нужно: почтовый сервис, репозиторий, HTTP-клиент. Создавать это руками в каждом слушателе — путь к дублированию и хаосу. Для этого есть контейнер.
Без своего контейнера¶
Ничего делать не нужно. Роутер сам поднимает PHP-DI с автосвязыванием: классы с зависимостями в конструкторе создаются без регистрации.
use Selyusize\EventsRouter\Container\Container;
use Selyusize\EventsRouter\Contract\Core\EventInterface;
use Selyusize\EventsRouter\Contract\Core\ListenerInterface;
use Selyusize\EventsRouter\Contract\Core\MiddlewareInterface;
final class Clock
{
public function now(): string
{
return '14:03';
}
}
final class Mailer
{
public function __construct(private readonly Clock $clock) {}
public function send(string $text): void
{
echo '[', $this->clock->now(), '] письмо: ', $text, PHP_EOL;
}
}
// Слушатель статичный: зависимости берёт из контейнера сам
final class SendConfirmationEmail implements ListenerInterface
{
public static function handle(EventInterface $event): void
{
Container::get(Mailer::class)->send('заказ ' . $event->getAttribute('order_id') . ' оформлен');
}
}
// Middleware получает зависимости через конструктор: контейнер создаст его сам
final class StampTime implements MiddlewareInterface
{
public function __construct(private readonly Clock $clock) {}
public function process(EventInterface $event, Closure $next): void
{
$next($event->withAttribute('received_at', $this->clock->now()));
}
}
use Selyusize\EventsRouter\Event;
use Selyusize\EventsRouter\EventRouterFactory;
$events = EventRouterFactory::create(); // без контейнера проекта — PHP-DI с автосвязыванием
$events->listen('shop.order.{order_id}.created', SendConfirmationEmail::class)
->add(StampTime::class); // Clock в конструктор подставит контейнер
$events->dispatch(new Event('shop.order.42.created'));
Здесь работают два механизма:
- Слушатель статичный, поэтому зависимости он берёт сам:
Container::get(Mailer::class).Container— статический фасад библиотеки,Selyusize\EventsRouter\Container\Container. - Middleware — обычный объект. Достаточно указать имя класса в
add(), а контейнер создаст его со всеми зависимостями в момент, когда событие дойдёт до этого middleware.
С контейнером проекта¶
Если в проекте уже есть контейнер, передайте его в фабрику:
С этого момента и middleware, и Container::get() в слушателях работают с вашим контейнером. Два контейнера не появятся.
В проекте на стартовой архитектуре с Rasa\Container\Container это выглядит так:
Теперь Rasa\Container\Container::get() и Selyusize\EventsRouter\Container\Container::get() смотрят в один и тот же объект. В слушателях можно писать так, как принято в проекте.
Другой контейнер¶
Роутеру подходит любой контейнер, реализующий PSR-11 Psr\Container\ContainerInterface. PHP-DI нужен библиотеке только как запасной вариант, когда своего контейнера нет. От контейнера роутер ждёт одного: get(ИмяКласса::class) возвращает объект этого класса.
| Контейнер | Что нужно сделать |
|---|---|
| PHP-DI (как в стартовой архитектуре) | ничего: создаёт любой класс сам |
Laravel (Illuminate\Container\Container) |
ничего: тоже создаёт классы сам |
| Symfony DependencyInjection | middleware и сервисы, которые слушатели берут через Container::get(), должны быть публичными сервисами с id, равным имени класса: по умолчанию сервисы Symfony приватные |
| простой контейнер «ключ → объект» | зарегистрировать каждый middleware, который указан в маршрутах именем класса |
Фасад Container — один на процесс. Если создать два роутера с разными контейнерами, Container::get() будет смотреть в последний переданный. В обычном приложении роутер один, и это не проблема.
Если контейнер не создаёт классы сам¶
PHP-DI создаёт любой класс без регистрации. Другие контейнеры так умеют не всегда. Если ваш контейнер работает только с тем, что в нём зарегистрировано, зарегистрируйте в нём и middleware, которые указаны в маршрутах именем класса.
Иначе контейнер бросит исключение, когда событие дойдёт до такого middleware. Слушатель за ним получит статус Failed, остальные отработают как обычно.
Если класса нет вовсе¶
Опечатку в имени класса роутер ловит сразу, ещё в listen() и add(), а не когда придёт событие. Исключение — InvalidRoute, в стеке вызовов видна строка файла маршрутов с ошибкой.