События

События позволяют одним частям приложения сообщать о произошедшем, не связывая их напрямую с кодом, который должен на это отреагировать. Например, после отправки заказа можно создать событие 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)
    {
        // ...
    }
}
PHP

Трейт HasDispatcher добавляет статический метод dispatch, позволяющий легко инициировать событие:

OrderShipped::dispatch($order);
PHP

Строковые события

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

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]);
PHP

Аргументы передаются слушателю по позиции. Имя события сравнивается точно. Один слушатель можно назначить сразу нескольким событиям:

Event::listen(
    ['order.shipped', 'order.cancelled'],
    AuditOrderChange::class
);
PHP

Создание слушателей

Замыкания

Для небольшого обработчика передайте замыкание методу 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,
            ]);
        });
    }
}
PHP

Классы слушателей

Если обработчик относится к самостоятельной задаче, вынесите его в класс, например 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
    {
        // Отправить покупателю уведомление о доставке.
    }
}
PHP

После, зарегистрируйте имя класса слушателя в 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 = [
        // ...
    ];
}
PHP

Подписчики событий (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
    {
        // ...
    }
}
PHP

Регистрация подписчиков событий

Чтобы зарегистрировать подписчика, укажите его класс в массиве $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
    ];
}
PHP

Отправка событий и результаты

Глобальная функция event и метод Event::dispatch равнозначны. Обычная отправка вызывает слушатели в порядке их регистрации и возвращает массив их результатов:

$responses = event('report.generate', [$report]);

// Например: [null, 'queued']
PHP

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

Получение первого результата

Когда важен первый содержательный ответ, используйте until:

use Imhotep\Facades\Event;

$result = Event::until('order.can-ship', [$order]);

if ($result === false) {
    // Один из слушателей запретил отправку.
}
PHP

until прекращает вызов слушателей после первого результата, отличного от null, и возвращает его. Если все слушатели вернули null или слушателей нет, метод вернёт null. Значение false также считается результатом и сразу останавливает обработку.