Skip to content

事件系统

事件系统(Event System)是现代 PHP 框架中实现松耦合通信的核心机制。它基于观察者模式(Observer Pattern),允许对象在特定动作发生时通知其他对象,而无需硬编码依赖关系。事件系统广泛应用于日志记录、通知推送、审计追踪、缓存清理等场景。本节将深入讲解事件系统的概念、PSR-14 标准和各种实现方式。

前置知识

阅读本节前,建议先了解:

基础概念

什么是事件系统

事件系统由三个核心组件构成:

事件(Event)     → 发生了什么
监听器(Listener) → 对事件做出反应
分发器(Dispatcher)→ 将事件分发给对应的监听器

事件系统的工作流程

1. 某个服务触发一个事件
    $dispatcher->dispatch(new UserRegisteredEvent($user));

2. 分发器查找该事件的所有监听器
    → UserRegisteredListener
    → WelcomeEmailListener
    → ActivityLogListener

3. 按优先级顺序调用每个监听器
    → UserRegisteredListener::handle()
    → WelcomeEmailListener::handle()
    → ActivityLogListener::handle()

4. 监听器处理事件(可修改事件数据)
5. 返回(可选:可终止传播)

详细说明

PSR-14 接口

php
<?php
namespace Psr\EventDispatcher;

interface EventDispatcherInterface
{
    /**
     * 分发事件,返回可能被修改后的事件
     */
    public function dispatch(object $event): object;
}

interface ListenerProviderInterface
{
    /**
     * 获取事件对应的所有监听器
     */
    public function getListenersForEvent(object $event): iterable;
}

interface StoppableEventInterface
{
    /**
     * 判断事件传播是否已停止
     */
    public function isPropagationStopped(): bool;

    /**
     * 停止事件传播
     */
    public function stopPropagation(): void;
}

事件定义

php
<?php
declare(strict_types=1);

namespace App\Events;

use App\Models\User;

class UserRegisteredEvent
{
    public function __construct(
        public readonly User $user,
        public readonly string $ip,
    ) {}

    public function getTimestamp(): int
    {
        return time();
    }
}

class OrderPlacedEvent
{
    public function __construct(
        public readonly int $orderId,
        public readonly float $amount,
        public readonly int $userId,
    ) {}
}

监听器定义

php
<?php
declare(strict_types=1);

namespace App\Listeners;

use App\Events\UserRegisteredEvent;
use App\Services\EmailService;
use Psr\Log\LoggerInterface;

class WelcomeEmailListener
{
    public function __construct(
        private readonly EmailService $emailService,
        private readonly LoggerInterface $logger,
    ) {}

    public function __invoke(UserRegisteredEvent $event): void
    {
        $user = $event->user;

        try {
            $this->emailService->sendWelcome($user->email, $user->name);
            $this->logger->info('Welcome email sent', [
                'user_id' => $user->id,
            ]);
        } catch (\Throwable $e) {
            $this->logger->error('Failed to send welcome email', [
                'user_id' => $user->id,
                'error' => $e->getMessage(),
            ]);
        }
    }
}

class ActivityLogListener
{
    public function __construct(
        private readonly LoggerInterface $logger
    ) {}

    public function __invoke(UserRegisteredEvent $event): void
    {
        $this->logger->info('User registered', [
            'user_id' => $event->user->id,
            'name' => $event->user->name,
            'email' => $event->user->email,
            'ip' => $event->ip,
            'timestamp' => $event->getTimestamp(),
        ]);
    }
}

事件分发器实现

php
<?php
declare(strict_types=1);

namespace App\Event;

use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\EventDispatcher\ListenerProviderInterface;
use Psr\EventDispatcher\StoppableEventInterface;

class SimpleEventDispatcher implements EventDispatcherInterface
{
    public function __construct(
        private readonly ListenerProviderInterface $listenerProvider
    ) {}

    public function dispatch(object $event): object
    {
        foreach ($this->listenerProvider->getListenersForEvent($event) as $listener) {
            if ($event instanceof StoppableEventInterface && $event->isPropagationStopped()) {
                return $event;
            }

            $listener($event);
        }

        return $event;
    }
}

class ListenerProvider implements ListenerProviderInterface
{
    /** @var array<class-string, array<int, callable>> */
    private array $listeners = [];

    /**
     * 注册监听器
     */
    public function addListener(string $eventName, callable $listener, int $priority = 0): void
    {
        if (!isset($this->listeners[$eventName])) {
            $this->listeners[$eventName] = [];
        }

        $this->listeners[$eventName][] = ['listener' => $listener, 'priority' => $priority];

        // 按优先级排序(数字越大优先级越高)
        usort($this->listeners[$eventName], fn($a, $b) => $b['priority'] <=> $a['priority']);
    }

    public function getListenersForEvent(object $event): iterable
    {
        $eventName = $event::class;

        // 支持事件继承
        $listeners = [];
        foreach ($this->listeners as $registeredEvent => $eventListeners) {
            if ($event instanceof $registeredEvent || $eventName === $registeredEvent) {
                foreach ($eventListeners as $entry) {
                    $listeners[] = $entry['listener'];
                }
            }
        }

        return $listeners;
    }
}

可停止传播的事件

php
<?php
declare(strict_types=1);

namespace App\Events;

use Psr\EventDispatcher\StoppableEventInterface;

class RequestEvent implements StoppableEventInterface
{
    private bool $propagationStopped = false;

    public function __construct(
        private readonly object $request,
        private ?object $response = null,
    ) {}

    public function getRequest(): object
    {
        return $this->request;
    }

    public function getResponse(): ?object
    {
        return $this->response;
    }

    public function setResponse(object $response): void
    {
        $this->response = $response;
        $this->stopPropagation();
    }

    public function isPropagationStopped(): bool
    {
        return $this->propagationStopped;
    }

    public function stopPropagation(): void
    {
        $this->propagationStopped = true;
    }
}

实战示例

场景一:订单处理事件

php
<?php
declare(strict_types=1);

namespace App\Events;

class OrderPlacedEvent
{
    public function __construct(
        public readonly int $orderId,
        public readonly int $userId,
        public readonly float $totalAmount,
        public readonly array $items,
    ) {}
}

// 监听器:发送确认邮件
class SendOrderConfirmation implements \Psr\EventDispatcher\ListenerProviderInterface
{
    public function getListenersForEvent(object $event): iterable
    {
        if ($event instanceof OrderPlacedEvent) {
            return [
                fn() => $this->sendConfirmation($event),
            ];
        }
        return [];
    }

    private function sendConfirmation(OrderPlacedEvent $event): void
    {
        echo "Sending confirmation for order #{$event->orderId}\n";
    }
}

// 监听器:更新库存
class UpdateInventoryListener
{
    public function __invoke(OrderPlacedEvent $event): void
    {
        foreach ($event->items as $item) {
            echo "Decreasing inventory for product {$item['product_id']}\n";
        }
    }
}

// 监听器:记录日志
class LogOrderListener
{
    public function __construct(private readonly \Psr\Log\LoggerInterface $logger) {}

    public function __invoke(OrderPlacedEvent $event): void
    {
        $this->logger->info('Order placed', [
            'order_id' => $event->orderId,
            'user_id' => $event->userId,
            'total' => $event->totalAmount,
        ]);
    }
}

场景二:注册和使用事件

php
<?php
declare(strict_types=1);

use App\Event\ListenerProvider;
use App\Event\SimpleEventDispatcher;

// 创建监听器提供者
$provider = new ListenerProvider();

// 注册监听器(按优先级排序)
$provider->addListener(
    OrderPlacedEvent::class,
    new LogOrderListener($logger),
    priority: 100      // 高优先级,先执行
);
$provider->addListener(
    OrderPlacedEvent::class,
    new UpdateInventoryListener(),
    priority: 50
);
$provider->addListener(
    OrderPlacedEvent::class,
    fn(OrderPlacedEvent $e) => /* 发送邮件 */ null,
    priority: 10        // 低优先级,后执行
);

// 创建分发器
$dispatcher = new SimpleEventDispatcher($provider);

// 触发事件
$event = new OrderPlacedEvent(
    orderId: 1001,
    userId: 42,
    totalAmount: 299.99,
    items: [['product_id' => 1, 'quantity' => 2]]
);

$dispatcher->dispatch($event);

注意事项

1. 同步 vs 异步

PHP 的事件系统通常是同步的(按顺序执行所有监听器)。如果监听器执行时间较长,会影响请求响应时间。对于耗时操作(如发送邮件),应使用队列异步处理。

php
<?php
// 同步监听器(默认,立即执行)
class SendWelcomeEmail
{
    public function __invoke(UserRegisteredEvent $event): void
    {
        Mail::to($event->user->email)->send(new WelcomeMail());
    }
}

// 异步监听器(通过队列处理,Laravel 方式)
class SendWelcomeEmail implements ShouldQueue
{
    public function __construct(
        public readonly User $user,
    ) {}

    public function handle(): void
    {
        Mail::to($this->user->email)->send(new WelcomeMail());
    }
}

2. 事件命名

php
<?php
// ✅ 推荐:使用过去分词表示已发生的事件
class UserRegisteredEvent {}     // 用户已注册
class OrderPlacedEvent {}          // 订单已下
class PaymentSucceededEvent {}     // 支付已成功
class PasswordChangedEvent {}      // 密码已修改

// ❌ 避免:使用不定式
class UserRegister {}              // 注册中?
class PlaceOrder {}                // 下单中?

3. 事件 vs 直接调用

php
<?php
// ❌ 直接调用(紧耦合)
class UserController
{
    public function register(array $data): User
    {
        $user = User::create($data);
        $this->emailService->sendWelcome($user);
        $this->logService->log('User registered', $user);
        $this->profileService->createDefault($user);
        return $user;
    }
}

// ✅ 使用事件(松耦合)
class UserController
{
    public function __construct(
        private readonly EventDispatcherInterface $dispatcher
    ) {}

    public function register(array $data): User
    {
        $user = User::create($data);
        $this->dispatcher->dispatch(new UserRegisteredEvent($user));
        return $user;
    }
}

4. 领域事件 vs 框架事件

php
<?php
// 领域事件:与业务逻辑相关,可被任何监听器处理
class OrderPlacedEvent
{
    public function __construct(
        public readonly Order $order,
    ) {}
}

// 框架事件:由框架触发,与框架生命周期相关
class RequestHandledEvent
{
    public function __construct(
        public readonly ServerRequestInterface $request,
        public readonly ResponseInterface $response,
        public readonly float $duration,
    ) {}
}

最佳实践

1. 事件应该是不可变的

php
<?php
declare(strict_types=1);

// ✅ 不可变事件
class UserRegisteredEvent
{
    public function __construct(
        public readonly User $user,
    ) {}
}

// ❌ 可变事件(可能导致副作用)
class BadEvent
{
    public User $user;

    public function __construct(User $user)
    {
        $this->user = $user;
    }
}

2. 监听器应该是幂等的

监听器应该可以安全地多次执行而不产生副作用。

下一节

继续学习:队列与任务调度 — 了解异步处理和定时任务的实现。

参考链接