本文目录导读:

- 基础方案:观察者模式 + 事件调度器
- 中级方案:使用 Symphony EventDispatcher 组件
- 高级方案:异步事件处理
- 最复杂场景:事件驱动 + CQRS/Event Sourcing
- 总结:如何选择?
在PHP中实现复杂事件处理,通常需要构建一个事件驱动架构,PHP虽然是同步阻塞语言,但通过合理的架构设计,依然可以优雅地处理复杂事件链。
以下是几种从基础到高级的实现方案:
基础方案:观察者模式 + 事件调度器
这是最常用且最容易理解的方式,核心思想是“松耦合”:事件生产者不直接调用消费者,而是通过一个中间调度器。
核心组件:
- Event(事件): 一个普通类,包含事件数据。
- Listener(监听器): 处理事件的逻辑。
- Dispatcher(调度器): 管理监听器,并触发事件。
简单实现示例:
<?php
// 1. 定义事件类
class UserRegisteredEvent {
private $userId;
public function __construct(string $userId) {
$this->userId = $userId;
}
public function getUserId(): string {
return $this->userId;
}
}
// 2. 定义监听器接口
interface EventListener {
public function handle($event): void;
}
// 3. 实现具体监听器
class SendWelcomeEmailListener implements EventListener {
public function handle($event): void {
echo "发送欢迎邮件给用户: " . $event->getUserId() . "\n";
}
}
class UpdateUserStatisticsListener implements EventListener {
public function handle($event): void {
echo "更新用户统计数据: " . $event->getUserId() . "\n";
}
}
// 4. 核心:事件调度器
class EventDispatcher {
private array $listeners = [];
// 注册监听器
public function addListener(string $eventName, EventListener $listener): void {
$this->listeners[$eventName][] = $listener;
}
// 触发事件
public function dispatch(object $event): void {
$eventName = get_class($event);
if (!isset($this->listeners[$eventName])) {
return;
}
// 遍历并执行所有监听器
foreach ($this->listeners[$eventName] as $listener) {
$listener->handle($event);
}
}
}
// 5. 使用示例
$dispatcher = new EventDispatcher();
$dispatcher->addListener(UserRegisteredEvent::class, new SendWelcomeEmailListener());
$dispatcher->addListener(UserRegisteredEvent::class, new UpdateUserStatisticsListener());
// 业务代码中触发事件
$dispatcher->dispatch(new UserRegisteredEvent('user123'));
// 输出:
// 发送欢迎邮件给用户: user123
// 更新用户统计数据: user123
优点: 简单、易理解、零依赖。
缺点: 同步执行,如果监听器很多或很慢,会阻塞主流程,不支持事件链(一个事件触发另一个事件)。
中级方案:使用 Symphony EventDispatcher 组件
这个组件是对上面方案的生产级实现,已经被 Symfony、Drupal 和 Laravel(借鉴)广泛使用,它支持事件优先级、事件传播停止等功能。
composer require symfony/event-dispatcher
示例:
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\Event;
// 自定义事件类
class OrderCreatedEvent extends Event {
const NAME = 'order.created';
public function __construct(private string $orderId) {}
public function getOrderId(): string {
return $this->orderId;
}
}
// 监听器
class SendNotificationListener {
public function onOrderCreated(OrderCreatedEvent $event): void {
echo "通知: 订单 {$event->getOrderId()} 已创建\n";
}
}
// 使用
$dispatcher = new EventDispatcher();
$dispatcher->addListener(OrderCreatedEvent::NAME, [new SendNotificationListener(), 'onOrderCreated']);
$dispatcher->dispatch(new OrderCreatedEvent('ORD-2024'), OrderCreatedEvent::NAME);
优点: 稳定、成熟,支持事件优先级和停止传播,易于与框架集成。
缺点: 仍然是同步执行。
高级方案:异步事件处理
对于“复杂”事件处理(如发送几十封邮件、生成PDF、调用外部API),同步执行会让用户等得太久,这时需要异步事件队列。
核心思路: 事件调度器只负责将事件存入队列(如 Redis、RabbitMQ、数据库),然后立即返回,后台工作进程(Worker)从队列中取事件并处理。
主流实现:
方案 A:使用 Laravel 队列(最省心)
如果你在用 Laravel,可以直接用它的 ShouldQueue trait:
// 事件类
class OrderShipped implements ShouldQueue {
public $connection = 'redis'; // 使用Redis队列
public $queue = 'high'; // 队列名称
public function __construct(public $orderId) {}
}
// 监听器(自动进队列)
class SendShipmentEmail implements ShouldQueue {
public $queue = 'high';
public function handle(OrderShipped $event): void {
// 耗时操作:发送邮件
Mail::to($event->order->email)->send(new OrderShippedMail($event->order));
}
}
方案 B:纯PHP + Redis 手动实现
如果你没有用框架,可以用 phpredis 或 predis:
// 生产者:事件调度器
class AsyncEventDispatcher {
private \Redis $redis;
public function dispatch(string $eventType, array $data): void {
$message = json_encode([
'type' => $eventType,
'data' => $data,
'timestamp' => time()
]);
// 推入 Redis 列表(队列)
$this->redis->rpush('event_queue', $message);
echo "事件已存入队列\n";
}
}
// 消费者:后台守护进程
class EventWorker {
public function run(): void {
while (true) {
// 阻塞式取出 (BLPOP)
$data = $this->redis->blpop(['event_queue'], 5);
if ($data) {
$event = json_decode($data[1], true);
$this->process($event); // 实际处理逻辑
}
}
}
}
用 supervisor 启动多个 Worker 进程即可实现并行的、异步的事件处理。
优点: 真正的异步,不阻塞主请求,可水平扩展(启动多个 Worker)。
缺点: 需要维护额外的基础设施(Redis/RabbitMQ/数据库 + Worker 进程)。
最复杂场景:事件驱动 + CQRS/Event Sourcing
如果系统极其复杂(比如电商、金融系统),需要事件溯源(Event Sourcing)来记录所有状态变更,可以引入 CQRS(命令查询职责分离)。
流程:
- 所有业务操作都生成不可变的领域事件。
- 事件存储到事件库(Event Store,可能是数据库或专用存储)。
- 事件通过消息队列广播。
- 多个投影(Projector)根据事件更新各自的读模型。
PHP 示例组件(生产级):
- prooph/event-sourcing: 完整的 PHP Event Sourcing 库。
- broadway/broadway: 另一个流行的 CQRS/ES 框架。
但这种模式的复杂度很高,仅推荐在确实需要(如审计、复杂状态回溯、多语言模型同步)时使用。
如何选择?
| 场景 | 推荐方案 | 依赖 | 异步能力 |
|---|---|---|---|
| 简单事件广播(如记录日志) | 自定义调度器 | 无 | 否 |
| 框架项目(Laravel/Symfony) | 框架自带事件系统或 symfony/event-dispatcher |
Composer | 可选(Laravel带队列) |
| 耗时操作(发邮件、通知) | 消息队列(Redis/RabbitMQ Worker) | Redis/PHP 扩展 + Supervisor | 是 |
| 极高并发、严格有序 | RabbitMQ + AMQP 扩展增强版 | RabbitMQ | 是 |
| 状态回溯、复杂审计 | CQRS + Event Sourcing | prooph/broadway | 是 |
最后建议: 对于大多数PHP项目,“同步事件调度器 + 异步消息队列”的组合是最平衡、最实用的方案,先用简单的事件调度器解耦逻辑,遇到性能瓶颈或需要异步处理时,再把特定监听器改造成队列任务。