本文目录导读:

在 PHP 项目中实现事件驱动解耦,核心目的是让模块之间不直接依赖,通过事件(Event)和监听器(Listener)进行通信,这样可以极大提升代码的可维护性、可扩展性和可测试性。
以下是系统化的实现方案,从基础到进阶,涵盖架构设计和具体代码示例。
核心概念
- 事件(Event):一个描述“发生了什么”的简单数据对象(如
OrderCreated),它包含相关数据(如订单ID)。 - 监听器(Listener):对特定事件做出反应的业务逻辑(如发送邮件、更新库存)。
- 调度器(Dispatcher):负责管理事件与监听器的对应关系,并触发事件(
dispatch($event))。
基础实现(不依赖框架)
在没有框架的情况下,我们可以自己写一个简单的调度器。
事件类(PSR-14 风格)
定义一个基础接口,约定事件都包含 getPayload() 或具体属性。
<?php
// app/Events/OrderCreated.php
namespace App\Events;
class OrderCreated
{
public function __construct(
public readonly int $orderId,
public readonly array $orderData = []
) {}
}
监听器接口
<?php
// app/Listeners/ListenerInterface.php
namespace App\Listeners;
use App\Events\OrderCreated; // 或使用通用接口
interface ListenerInterface
{
public function handle(object $event): void;
}
调度器(核心)
<?php
// app/Core/EventDispatcher.php
namespace App\Core;
class EventDispatcher
{
private array $listeners = [];
// 注册监听器
public function listen(string $eventClass, callable|array $listener, int $priority = 0): void
{
$this->listeners[$eventClass][$priority][] = $listener;
ksort($this->listeners[$eventClass]); // 按优先级排序
}
// 触发事件
public function dispatch(object $event): void
{
$eventClass = get_class($event);
if (!isset($this->listeners[$eventClass])) {
return; // 无监听器直接返回
}
foreach ($this->listeners[$eventClass] as $priorityListeners) {
foreach ($priorityListeners as $listener) {
if (is_callable($listener)) {
$listener($event);
} elseif (is_array($listener)) {
[$object, $method] = $listener;
$object->$method($event);
}
}
}
}
}
业务使用
<?php
// 启动时注册(bootstrap.php 或 ServiceProvider)
$dispatcher = new EventDispatcher();
// 方案A:使用闭包(简单场景)
$dispatcher->listen(OrderCreated::class, function (OrderCreated $event) {
// 发送邮件逻辑
echo "发送订单邮件: {$event->orderId}";
});
// 方案B:使用类(复杂逻辑)
$dispatcher->listen(OrderCreated::class, [new UpdateInventoryListener(), 'handle']);
// 业务代码中触发事件
class OrderService
{
public function __construct(private EventDispatcher $dispatcher) {}
public function createOrder(array $data): void
{
$orderId = 123; // 假设创建成功
$this->dispatcher->dispatch(new OrderCreated($orderId, $data));
}
}
主流程解耦进阶:中间件与管道
场景:订单创建时,需要依次执行“库存扣减”、“优惠券核销”、“发送通知”,如果不使用事件,我们会写出以下耦合代码:
class OrderService
{
public function create(array $data): void
{
$order = $this->orders->create($data);
// 这里直接依赖了其他服务
$this->inventory->decrement($data['items']);
$this->coupons->consume($data['couponId']);
$this->notifier->send($order);
}
}
解耦方案:使用中间件模式(管道),这种模式比事件更适合“必须按顺序执行且需要传递上下文”的场景。
<?php
// 1. 定义中间件接口
interface OrderMiddleware
{
public function handle(array $orderData, \Closure $next): mixed;
}
// 2. 实现具体中间件
class InventoryMiddleware implements OrderMiddleware
{
public function handle(array $orderData, \Closure $next): mixed
{
// 执行前置操作
$this->inventory->decrement($orderData['items']);
// 调用下一个中间件
$result = $next($orderData);
// 执行后置操作
// ...
return $result;
}
}
// 3. 核心管道处理器
class Pipeline
{
public function process($data, array $middlewares, \Closure $destination)
{
$pipeline = array_reduce(
array_reverse($middlewares),
fn($nextStack, $middleware) => fn($data) => $middleware->handle($data, $nextStack),
$destination
);
return $pipeline($data);
}
}
// 4. 使用
$pipeline = new Pipeline();
$result = $pipeline->process($orderData, [
new InventoryMiddleware(),
new CouponMiddleware(),
new NotificationMiddleware(),
], function ($data) {
// 最终目的地:这里是真正的订单创建逻辑
echo "订单创建完毕";
});
生产级框架方案
在实际项目中,直接使用现成的成熟组件会更好,主流框架(Laravel / Symfony)已内置此功能。
Laravel(最常用的方案)
// 定义事件
class OrderCreated
{
use Dispatchable;
public function __construct(public Order $order) {}
}
// 定义监听器
class SendOrderEmail
{
public function handle(OrderCreated $event): void
{
// 发送邮件
}
}
// 注册映射(EventServiceProvider.php)
protected $listen = [
OrderCreated::class => [
SendOrderEmail::class,
UpdateInventory::class,
],
];
// 触发事件
OrderCreated::dispatch($order);
// 或者
event(new OrderCreated($order));
Symfony EventDispatcher(组件化)
use Symfony\Component\EventDispatcher\EventDispatcher;
$dispatcher = new EventDispatcher();
$dispatcher->addListener(OrderCreated::class, function (OrderCreated $event) {
// 业务逻辑
}, 10); // 优先级10
// 触发
$dispatcher->dispatch(new OrderCreated($order), OrderCreated::class);
事件驱动架构陷阱与最佳实践
“伪解耦”风险
注意:事件虽然让你不直接调用对方的类,但如果事件监听器里写了重逻辑(如数据库查询、调用外部API),本质仍然是同步阻塞。
解决:如果需要真正的异步(高并发),应结合消息队列(Redis Stream / RabbitMQ / Kafka)。
// 解耦升级路径 同步事件(基础) --> 队列驱动(Queue::push) --> 消息队列(RabbitMQ)
数据共享方式
- 不要传整个 Model 对象(如
$order),传递 orderId + 快照数据,否则监听器在异步处理时,原对象已过期。 - 使用 DTO(数据传输对象)
OrderCreatedPayload,包含order_id, user_name, total_amount等必要字段。
错误处理
- 监听器内部必须try-catch,防止一个监听器失败导致整条事件链断裂。
- 在 Laravel 中可以使用
ShouldQueue监听器,配合failed()方法记录失败日志。
事件命名与粒度
- 使用过去时表示已发生(
OrderCreated)。 - 触发事件应放在业务状态变更成功后(事务提交后),避免失败回滚后仍发邮件。
什么时候选择事件?
| 场景 | 推荐方案 |
|---|---|
| A模块需要知道B模块的动作 | 事件(EventDispatcher) |
| 必须保证流程顺序(不允许跳步) | 管道/中间件(Pipeline) |
| 需要异步处理且高吞吐 | 消息队列(RabbitMQ/Kafka) |
| 解耦 + 异步 + 可靠投递 | 事件驱动 + 消息队列 |
代码演进路线:
原生闭包回调 → 手写EventDispatcher → 使用Symfony Component → 集成Laravel Queue(队列异步化) → 引入消息中间件。
推荐:如果项目已经使用 Laravel/Symfony,直接使用框架内置事件并开启队列驱动;如果是 PHP 原生或轻框架,引入 symfony/event-dispatcher 是最优雅、轻量的选择(功能完整且经过大规模验证)。