Event и Listener в Laravel
Glossary overview

Event и Listener в Laravel

Event — это факт, что что-то произошло. Например: «пользователь зарегистрировался», «заказ создан», «платёж прошёл».

Listener — это реакция на этот факт. Что нужно сделать, когда событие произошло.

Проблема: всё в одном сервисе

Без Event/Listener побочные эффекты накапливаются прямо в сервисе. Создание заказа — одно действие, а email, Slack, бонусы, статистика — побочные эффекты, которые к нему налипли.

class OrderService
{
    public function create(CreateOrderDTO $dto): Order
    {
        $order = Order::create([...]);

        // Побочные эффекты растут с каждой новой задачей
        Mail::send(...);
        Slack::notify(...);
        $user->addBonusPoints(...);
        Stats::increment(...);

        return $order;
    }
}

С каждым новым требованием метод разрастается. Сервис начинает знать обо всём — email, Slack, бонусы — хотя его задача только создать заказ.

Решение: Event + Listener

Сервис просто говорит «заказ создан» — и больше ни о чём не думает:

class OrderService
{
    public function create(CreateOrderDTO $dto): Order
    {
        $order = Order::create([...]);

        // Одна строка вместо четырёх блоков
        event(new OrderCreated($order));

        return $order;
    }
}

Event — контейнер с данными

Event — это простой класс, который хранит данные о том, что произошло. Никакой логики — только свойства. Создаётся командой php artisan make:event OrderCreated.

// app/Events/OrderCreated.php

namespace App\Events;

use App\Models\Order;

class OrderCreated
{
    public function __construct(
        public readonly Order $order,
    ) {}
}

Listener — реакция на событие

Каждая реакция — отдельный класс с методом handle(). Listener получает Event и делает свою работу. Создаётся командой php artisan make:listener SendOrderEmail.

// app/Listeners/SendOrderEmail.php

namespace App\Listeners;

use App\Events\OrderCreated;
use App\Mail\OrderConfirmation;
use Illuminate\Support\Facades\Mail;

class SendOrderEmail
{
    public function handle(OrderCreated $event): void
    {
        Mail::to($event->order->user->email)->send(
            new OrderConfirmation($event->order)
        );
    }
}
// app/Listeners/NotifyManagerInSlack.php

namespace App\Listeners;

use App\Events\OrderCreated;
use Illuminate\Support\Facades\Slack;

class NotifyManagerInSlack
{
    public function handle(OrderCreated $event): void
    {
        Slack::channel('orders')->send(
            "Новый заказ #{$event->order->id}"
        );
    }
}
// app/Listeners/AddBonusPoints.php

namespace App\Listeners;

use App\Events\OrderCreated;

class AddBonusPoints
{
    public function handle(OrderCreated $event): void
    {
        $user = $event->order->user;
        $points = (int) ($event->order->total_price * 0.1);

        $user->increment('bonus_points', $points);
    }
}

Регистрация: связь Event → Listener

Laravel должен знать, какие Listener-ы реагируют на какие Event-ы. Это указывается в AppServiceProvider:

// app/Providers/AppServiceProvider.php

namespace App\Providers;

use App\Events\OrderCreated;
use App\Listeners\SendOrderEmail;
use App\Listeners\NotifyManagerInSlack;
use App\Listeners\AddBonusPoints;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Event::listen(OrderCreated::class, SendOrderEmail::class);
        Event::listen(OrderCreated::class, NotifyManagerInSlack::class);
        Event::listen(OrderCreated::class, AddBonusPoints::class);
    }
}

Теперь один вызов event(new OrderCreated($order)) запускает три Listener-а. Нужна новая реакция — создаёшь ещё один Listener и регистрируешь. Сервис вообще не трогаешь.

Структура файлов

app/
├── Events/
│   └── OrderCreated.php              ← что произошло
├── Listeners/
│   ├── SendOrderEmail.php            ← реакция: email клиенту
│   ├── NotifyManagerInSlack.php      ← реакция: сообщение в Slack
│   └── AddBonusPoints.php            ← реакция: начислить бонусы
├── Services/
│   └── OrderService.php              ← вызывает event()
└── Providers/
    └── AppServiceProvider.php        ← связывает Event → Listener

Аналогия с WordPress

По сути это тот же принцип, что хуки в WordPress — только типизированный и с классами:

// WordPress
do_action('order_created', $order);
add_action('order_created', 'send_order_email');
add_action('order_created', 'notify_slack');

// Laravel
event(new OrderCreated($order));
Event::listen(OrderCreated::class, SendOrderEmail::class);
Event::listen(OrderCreated::class, NotifyManagerInSlack::class);

Разница: в WordPress хук — это строка 'order_created', легко опечататься. В Laravel Event — это класс с тайп-хинтом, IDE подскажет и подчеркнёт ошибку.

Асинхронные Listener-ы (очереди)

По умолчанию Listener-ы выполняются синхроннопользователь ждёт, пока отправится email и сообщение в Slack. Чтобы выполнить реакцию в фоне, достаточно добавить интерфейс ShouldQueue:

use Illuminate\Contracts\Queue\ShouldQueue;

class SendOrderEmail implements ShouldQueue
{
    public function handle(OrderCreated $event): void
    {
        // Этот код выполнится в фоне, через очередь
        Mail::to($event->order->user->email)->send(
            new OrderConfirmation($event->order)
        );
    }
}

Одна строка — implements ShouldQueue — и Listener уходит в очередь. Пользователь получает ответ мгновенно, а email отправляется в фоне.