Event Bus и паттерн Observer в PHP
В современной разработке веб-приложений на PHP часто возникает необходимость гибко реагировать на различные события: отправка email после регистрации, логирование действий пользователя, обновление кэша при изменении данных. Для этих целей идеально подходят паттерн Observer и его расширение — Event Bus (шина событий).
Паттерн Observer: основы
Паттерн Observer (Наблюдатель) — поведенческий шаблон проектирования, который создаёт механизм подписки, позволяющий одним объектам следить и реагировать на события, происходящие в других объектах. Основные компоненты:
- Subject (Субъект) — объект, генерирующий события
- Observer (Наблюдатель) — объект, реагирующий на события
- Event (Событие) — информация о произошедшем действии
Event Bus: эволюция Observer
Event Bus — это централизованная шина, которая управляет всеми событиями в приложении. В отличие от классического Observer, где подписка жёстко привязана к субъекту, Event Bus предоставляет:
- Слабое связывание отправителей и получателей
- Возможность добавлять/удалять обработчики во время выполнения
- Глобальный доступ к шине из любой части приложения
- Упрощённое тестирование и отладку
Пример реализации Event Bus на PHP
Создадим простую, но функциональную реализацию Event Bus, совместимую с PSR-14 (Event Dispatcher).
<?php
// Интерфейс события
interface EventInterface
{
public function getName(): string;
}
// Базовое событие
class UserRegisteredEvent implements EventInterface
{
public function __construct(
private string $email,
private string $username
) {}
public function getName(): string
{
return 'user.registered';
}
public function getEmail(): string
{
return $this->email;
}
public function getUsername(): string
{
return $this->username;
}
}
// Интерфейс обработчика
interface EventHandlerInterface
{
public function handle(EventInterface $event): void;
}
// Event Bus
class EventBus
{
private array $listeners = [];
public function addListener(string $eventName, EventHandlerInterface $handler): void
{
$this->listeners[$eventName][] = $handler;
}
public function removeListener(string $eventName, EventHandlerInterface $handler): void
{
if (!isset($this->listeners[$eventName])) {
return;
}
$this->listeners[$eventName] = array_filter(
$this->listeners[$eventName],
fn($h) => $h !== $handler
);
}
public function dispatch(EventInterface $event): void
{
$eventName = $event->getName();
if (!isset($this->listeners[$eventName])) {
return;
}
foreach ($this->listeners[$eventName] as $handler) {
$handler->handle($event);
}
}
}
?>
Практическое использование
Теперь создадим обработчики и продемонстрируем работу шины:
<?php
// Обработчик отправки email
class SendWelcomeEmailHandler implements EventHandlerInterface
{
public function handle(EventInterface $event): void
{
if (!$event instanceof UserRegisteredEvent) {
return;
}
echo "Отправка приветственного письма на {$event->getEmail()}\n";
// mail($event->getEmail(), 'Добро пожаловать!', '...');
}
}
// Обработчик логирования
class LogUserRegistrationHandler implements EventHandlerInterface
{
public function handle(EventInterface $event): void
{
if (!$event instanceof UserRegisteredEvent) {
return;
}
echo "Логирование: пользователь {$event->getUsername()} зарегистрирован\n";
// file_put_contents('logs.txt', "...", FILE_APPEND);
}
}
// Настройка и использование
$bus = new EventBus();
$bus->addListener('user.registered', new SendWelcomeEmailHandler());
$bus->addListener('user.registered', new LogUserRegistrationHandler());
// Генерация события
$event = new UserRegisteredEvent('user@example.com', 'john_doe');
$bus->dispatch($event);
// Вывод:
// Отправка приветственного письма на user@example.com
// Логирование: пользователь john_doe зарегистрирован
?>
Расширенная версия с приоритетами
Добавим возможность задавать приоритет обработчиков:
<?php
class PriorityEventBus extends EventBus
{
public function addListener(string $eventName, EventHandlerInterface $handler, int $priority = 0): void
{
$this->listeners[$eventName][] = [
'handler' => $handler,
'priority' => $priority
];
// Сортировка по приоритету (выше = раньше)
usort($