Передать данные объектом¶
Массив в payload удобен ровно до тех пор, пока не нужно вспомнить, какие в нём ключи. Через месяц $payload['amount'] превращается в угадайку: есть ли там currency, это рубли или копейки, а order_id строкой или числом?
Решение — DTO с именованным конструктором, который сам собирает имя события:
use Selyusize\EventsRouter\Event;
final class OrderPaid
{
public function __construct(
public readonly int $orderId,
public readonly int $amount,
) {}
/**
* @return Event<self>
*/
public static function event(int $orderId, int $amount): Event
{
return new Event('shop.order.' . $orderId . '.paid', new self($orderId, $amount));
}
}
Имя события пишется один раз, рядом с данными, а не в каждом вызове dispatch():
use Selyusize\EventsRouter\EventRouterFactory;
$events = EventRouterFactory::create();
$events->listen('shop.order.{order_id}.paid', SendReceipt::class);
$events->dispatch(OrderPaid::event(42, 1500));
Слушатель получает тот же объект через getPayload():
use Selyusize\EventsRouter\Contract\Core\EventInterface;
use Selyusize\EventsRouter\Contract\Core\ListenerInterface;
final class SendReceipt implements ListenerInterface
{
public static function handle(EventInterface $event): void
{
$order = $event->getPayload();
assert($order instanceof OrderPaid);
echo 'Чек по заказу ', $order->orderId, ' на ', $order->amount, ' ₽', PHP_EOL;
}
}
Что это даёт:
- в месте рассылки видно, какое событие уйдёт;
- поля и их типы описаны в одном классе, IDE и Psalm их знают;
EventInterfaceобъявлен с шаблоном@template TPayload, поэтому уEvent<OrderPaid>статический анализ знает тип payload.
Почему библиотека не собирает имя события из класса сама — в Как это устроено.
Если DTO реализует PSR-14 StoppableEventInterface, слушатель может остановить рассылку — см. Обработать ошибки.