应直接使用 symfony/event-dispatcher 而非 SPL 或手写观察者模式,因其支持事件名过滤、任意 Event 对象传参、优先级控制、传播终止及异常隔离,避免内存泄漏与职责混乱。

别从 SPL 的 SplSubject 和 SplObserver 入手学观察者模式——PHP 8.2+ 已标记为 @deprecated,且它们只提供空接口,不带任何实现逻辑,强行用只会让你卡在“注册了但没通知”“通知了但传不了数据”这种基础问题上。
为什么原生写个 Subject/Observer 类容易跑偏
手写接口+实现类看似自由,但很快会暴露设计盲区:
- 所有观察者共用一个
notify()调用,没法区分「用户注册成功」和「订单支付完成」这类不同事件,只能靠 if-else 分流,越写越像回调地狱 -
update()方法参数固定是$subject,业务数据(比如$order或$user)得塞进 subject 属性里,导致 subject 变成状态筐,职责混乱 - 监听器注册后无法按事件名移除,CLI 长进程里反复 attach 同一个回调,内存泄漏肉眼可见
- 一个监听器抛异常,整个 notify 流程中断,后续监听器全被跳过,而你可能根本没加 try/catch
直接用 symfony/event-dispatcher 是最省心的路径
它不是“又一个组件”,而是当前 PHP 生态事实标准的事件分发方案,解决的就是原生观察者模式缺的那几块砖:
- 事件名(
'user.registered')作为第一级过滤条件,监听器只响应自己关心的字符串 - 支持传递任意
Event对象(可继承Symfony\Contracts\EventDispatcher\Event或直接用空对象),payload 自由封装 -
addListener('user.registered', [$this, 'onUserRegistered'], 10)中的数字是优先级,数值越大越先执行 -
$event->stopPropagation()可主动终止后续监听器调用,适合权限校验失败等场景
安装只要一条命令:composer require symfony/event-dispatcher。不用重写、不用抽象类、不用猜接口怎么定义。
立即学习“PHP免费学习笔记(深入)”;
如果非要手写轻量版,必须守住这四条线
不是不能写,而是写之前得把坑位标清楚:
- 存储结构必须是二维数组:
$listeners['event.name'] = [[$obj, 'method'], function() {}],一维数组根本做不到按名 dispatch -
dispatch($eventName, ...$args)接口要支持变参,或者强制要求传Event对象——别让每个新事件都去改方法签名 - 每个监听器调用必须包在
try/catch里,异常记录日志但不 throw 出去,否则链式执行就断了 - 必须提供
removeListener($eventName, $callback),尤其注意匿名函数无法 remove,得用spl_object_hash()或命名标识来管理
真正难的从来不是“怎么写 notify”,而是“怎么让事件名有语义、监听器可卸载、错误不雪崩、扩展不改旧代码”。这些细节不提前想透,写出来的观察者模式,半年后就会变成别人重构时第一个删掉的目录。



















