能,但不推荐在新项目中直接依赖;SplObserver和SplSubject仅提供接口契约,无通知逻辑实现,不支持传参、优先级、异步,PHP 8.0+已软弃用。

PHP内置的SplObserver和SplSubject能直接用吗?
能,但不推荐在新项目里直接依赖。PHP 5.3 引入的这两个接口只是骨架,没有实现通知分发逻辑,SplSubject连attach()、notify()都得自己写,而且不支持传参、不支持优先级、不支持异步——它只是个命名契约。
常见错误现象:Fatal error: Class Observer must implement interface SplObserver,但写了接口却收不到通知,因为忘了在Subject里手动遍历$observers并调用update()。
- 所有观察者必须实现
update(SplSubject $subject)方法,参数固定,无法附带事件名或数据载荷 -
SplSubject不提供detach()默认实现,容易内存泄漏(尤其长期运行的CLI进程) - PHP 8.0+ 已标记
SplSubject为“软弃用”,文档里不再鼓励使用
不用SplSubject,怎么手写一个轻量Subject类?
核心就三件事:存观察者、加/删观察者、触发通知。关键是把数据传进去,别卡死在SplSubject签名上。
使用场景:配置变更广播、日志钩子、表单提交后多步骤处理(如发邮件+写审计日志+更新缓存)。
立即学习“PHP免费学习笔记(深入)”;
class EventSubject
{
private array $observers = [];
public function attach(callable $observer): void
{
$this->observers[] = $observer;
}
public function notify(mixed $event, array $context = []): void
{
foreach ($this->observers as $observer) {
$observer($event, $context);
}
}
}
-
attach()接受callable,比强制实现接口更灵活(闭包、静态方法、对象方法都行) -
notify()参数开放,$event可以是字符串(如'user.registered')、对象或数组 - 如果需要解耦,
$context里塞['user_id' => 123]比让观察者自己从$subject里取更清晰
为什么__invoke比接口更实用?
因为观察者本质是“响应动作”,不是“一类东西”。用可调用类型 + 文档约定,比硬套SplObserver更贴近实际协作方式。
性能影响:无差异;兼容性:PHP 5.3+ 全支持。
- 类里定义
__invoke()方法,实例本身就能当观察者:$subject->attach(new UserEmailNotifier()) - 匿名函数最简捷:
$subject->attach(fn($e, $c) => error_log("Event: {$e}")) - 避免“为接口而接口”:比如一个观察者只处理
'cache.cleared',没必要实现整个SplObserver契约
事件名用字符串还是常量?
字符串。常量看着规范,但增加维护成本,且无法动态拼接(如UserDeleted::class . '.hard')。
容易踩的坑:拼错事件名导致静默失败;没做isset()检查就foreach空数组。
- 建议统一前缀,比如
'auth.login.success'、'order.payment.failed',方便调试时str_starts_with()过滤 - 上线前加一层防御:
if (empty($this->observers)) { return; },避免空循环浪费CPU - 如果事件高频(如每秒上千次),考虑用
spl_object_hash()做观察者去重,防止重复attach()
真正难的不是注册和通知,是怎么让观察者知道自己该响应哪个事件、怎么安全地清理自己——这些都得靠命名规范和团队约定,不是接口能管住的。



















