События
События позволяют одним частям приложения сообщать о произошедшем, не связывая их напрямую с кодом, который должен на это отреагировать. Например, после отправки заказа можно создать событие OrderShipped, а его обработку (отправку уведомлений, запись в журнал, обновление статистики) вынести в отдельные слушатели. Это делает код более гибким и слабосвязанным.
Создание событий
Классы событий
Событие – это простой класс с данными, который понадобится слушателям. Создайте его, например, в app/Events/OrderShipped.php:
<?php
namespace App\Events;
use App\Models\Order;
use Imhotep\Events\HasDispatcher;
class OrderShipped
{
use HasDispatcher;
public function __construct(public Order $order)
{
// ...
}
}
Трейт HasDispatcher добавляет статический метод dispatch, позволяющий легко инициировать событие:
OrderShipped::dispatch($order);
Строковые события
Для простых событий, которым не нужен отдельный класс, используйте строковое имя и массив аргументов:
use Imhotep\Facades\Event;
Event::listen('order.shipped', function (int $orderId, string $trackingNumber): void {
// ...
});
event('order.shipped', [$order->id, $order->tracking_number]);
Event::dispatch('order.shipped', [$order->id, $order->tracking_number]);
Аргументы передаются слушателю по позиции. Имя события сравнивается точно. Один слушатель можно назначить сразу нескольким событиям:
Event::listen(
['order.shipped', 'order.cancelled'],
AuditOrderChange::class
);
Создание слушателей
Замыкания
Для небольшого обработчика передайте замыкание методу listen фасада Event:
namespace App\Providers;
use App\Events\OrderShipped;
use Imhotep\Facades\Event;
use Imhotep\Facades\Log;
use Imhotep\Framework\Providers\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
Event::listen(OrderShipped::class, function (OrderShipped $event): void {
Log::info('Заказ передан в доставку', [
'order_id' => $event->order->id,
]);
});
}
}
Классы слушателей
Если обработчик относится к самостоятельной задаче, вынесите его в класс, например app/Listeners/SendShipmentNotification.php:
<?php
namespace App\Listeners;
use App\Events\OrderShipped;
use App\Services\OrderService;
use Imhotep\Container\Container;
class SendShipmentNotification
{
public function __construct(private OrderService $orderService)
{
// Класс поддерживает внедрение зависимостей
}
public function handle(OrderShipped $event): void
{
// Отправить покупателю уведомление о доставке.
}
}
После, зарегистрируйте имя класса слушателя в App\Providers\EventServiceProvider вашего приложения:
namespace App\Providers;
use App\Events\OrderShipped;
use App\Listeners\SendShipmentNotification;
use Imhotep\Events\EventServiceProvider as EventServiceProviderBase;
class EventServiceProvider extends EventServiceProviderBase
{
protected array $listen = [
OrderShipped::class => [
SendShipmentNotification::class,
],
['order.shipped', 'order.cancelled'] => function() {
// ...
}
function (OrderShipped $event) {
// ...
}
];
protected array $subscribe = [
// ...
];
}
Подписчики событий (Event Subscribers)
Когда вы работаете с группой событий, относящихся к одной предметной области (например, жизненный цикл заказа или управление пользователями), регистрация каждого слушателя по отдельности в EventServiceProvider может сделать файл громоздким.
Подписчик событий решает эту проблему: он позволяет сгруппировать регистрацию всех связанных слушателей в одном классе. Это делает код более организованным, упрощает его поддержку и уменьшает количество строк в сервис-провайдере.
Создание подписчика
Создайте, например, класс OrderEventSubscriber в каталоге app/Listeners. Он должен содержать публичный метод subscribe, единственным аргументом которого является объект типа Dispatcher. Внутри метода вы можете непосредственно вызывать $events->listen(...), либо вернуть массив в формате «событие => обработчик» (или список обработчиков). В конструкторе класса можно внедрить зависимости, необходимые для обработки событий.
<?php
namespace App\Listeners;
use App\Events\OrderCreated;
use App\Events\OrderShipped;
use App\Services\OrderService;
use Imhotep\Container\Container;
use Imhotep\Contracts\Events\Dispatcher;
class OrderEventSubscriber
{
public function __construct(private OrderService $orderService)
{
}
public function subscribe(Dispatcher $events): array
{
// Регистрация слушателя напрямую (опционально)
$events->listen(OrderCreated::class, function (OrderCreated $event): void {
// ...
});
// Регистрация методов подписчика через возвращаемый массив (опционально)
return [
OrderShipped::class => 'whenOrderShipped',
'order.cancelled' => 'whenOrderCancelled',
];
}
public function whenOrderShipped(OrderShipped $event): void
{
// ...
}
public function whenOrderCancelled(int $orderId): void
{
// ...
}
}
Регистрация подписчиков событий
Чтобы зарегистрировать подписчика, укажите его класс в массиве $subscribe вашего EventServiceProvider.
namespace App\Providers;
use App\Listeners\OrderEventSubscriber;
use Imhotep\Events\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider
{
protected array $listen = [
// ...
];
protected array $subscribe = [
OrderEventSubscriber::class
];
}
Отправка событий и результаты
Глобальная функция event и метод Event::dispatch равнозначны. Обычная отправка вызывает слушатели в порядке их регистрации и возвращает массив их результатов:
$responses = event('report.generate', [$report]);
// Например: [null, 'queued']
Если слушателей нет, результатом будет пустой массив. Слушатель может вернуть false, чтобы прекратить дальнейшее распространение события. Само значение false в массив результатов не попадёт; будут возвращены результаты обработчиков, выполненных до него.
Получение первого результата
Когда важен первый содержательный ответ, используйте until:
use Imhotep\Facades\Event;
$result = Event::until('order.can-ship', [$order]);
if ($result === false) {
// Один из слушателей запретил отправку.
}
until прекращает вызов слушателей после первого результата, отличного от null, и возвращает его. Если все слушатели вернули null или слушателей нет, метод вернёт null. Значение false также считается результатом и сразу останавливает обработку.