vendor 内的目录加入自动发现也能正常工作的结构,就可以减少安装、更新和创建缓存时的前提条件。
本页会确认 Laravel Framework v13.36.0 的实现,将事件的基本操作应用到包的长期维护中。Provider 的加载请参阅包自动发现,测试环境请参阅包的测试。
显式注册包的监听器
包默认执行的处理,可以在普通Illuminate\Support\ServiceProvider 的 boot() 中通过 Event::listen() 注册。Laravel 默认的监听器自动发现位置是应用的 app/Listeners,并不会自动扫描到包内的 src/Listeners。
作为示例,我们来创建一个将配送完成事件记录到日志的包。事件只是一个持有通知所需值的类。
handle() 中处理事件。
extra.laravel.providers,或由用户进行显式注册。如果在 HTTP 处理和队列 Worker 中都需要该事件,就不要用 runningInConsole() 限制注册。
注册与实例创建发生在不同时机
Dispatcher::listen() 会将针对普通事件名的监听器添加到内部数组中。像上面那样传入类名时,此时并不会创建监听器的实例。
对于同步监听器,在事件触发后,createClassCallable() 会使用容器的 make() 解析类。因此可以利用构造函数的依赖注入。
这条路径并不会对 handle() 执行容器的 call()。应设计为将任意服务注入到构造函数中,handle() 只接收事件。请不要将其视为与 Artisan 命令向 handle() 的依赖注入相同的机制。
这里说明的是同步监听器。实现 ShouldQueue 时会走队列专用的另一条路径,因此还需要就事务、重试和依赖解析确认事件的队列处理和队列。
event:cache 不会阻止显式注册
event:cache 并不是将事件 Dispatcher 中已注册的所有监听器整体序列化的命令。
EventCacheCommand::getEvents() 会列举 Illuminate\Foundation\Support\Providers\EventServiceProvider 及其派生 Provider,并保存各 Provider 的自动发现结果和 listens() 的映射。写在普通 ServiceProvider::boot() 中的 Event::listen() 不在该收集范围内。
另一方面,即使在存在缓存的启动中,普通 Provider 的 boot() 也会被执行。不包含在缓存中,与存在缓存时不会被注册,是两回事。
也可以在继承 Foundation
EventServiceProvider 的包中使用 $listen。但是,在所确认的实现中,shouldDiscoverEvents() 的默认值是针对该基类本身的判断。仅仅继承,并不会让该包的监听器目录被自动发现。
此外,该 Provider 的 $subscribe 和 $observers 是在注册 getEvents() 结果的循环之外另行处理的。请不要只看 $listen 的缓存,就认为这些注册也会被省略。
避免与自动发现的重复注册
如果用户通过withEvents(discover: [...]) 添加了包的监听器目录,而包的 Provider 也显式注册了同一个监听器,那么一个事件可能会导致处理执行多次。
Dispatcher::listen() 对普通事件执行的是向数组追加,在整个 Dispatcher 范围内不会自动对相同类名去重。Foundation EventServiceProvider 在自身映射内执行的 array_unique(),也不是用来合并其他 Provider 显式注册的机制。
请将注册的责任确定在一方。
- 由包注册默认监听器时,不要求用户为该目录添加自动发现。
- 由用户选择所需监听器时,包提供事件和监听器类,默认不注册相同的监听器。
- 从自动发现迁移到显式注册时,也要将用户端的发现配置和旧的事件缓存作为更新对象加以说明。
验证更新前后的行为
要检查注册状态,请在引入了包的验证用应用中执行event:list。确认在无缓存和有缓存两种情况下目标监听器都存在。
event:list 的显示,无法保证监听器能完成处理或只执行一次。还要添加使用实际事件触发的测试。
作为示例,在 Testbench 的基础 TestCase 中注册 CourierServiceProvider 后,验证 Logger 只被调用一次。
Event::fake() 便于确认事件是否被触发,但通常会阻止真实监听器的执行。本测试要验证注册和处理,因此不使用它。与自动发现的组合以及存在缓存时的启动,也要在另外的验证用应用或新的应用实例中确认。
长期维护检查清单
- 将事件类名、公共属性和监听器的注册方针作为面向用户的约定写入文档。
- 更改用户所订阅事件的名称或负载时,按照版本兼容性管理说明变更。
- 对于涉及自动发现或
$listen变更的更新,将重新创建event:cache纳入部署流程。 - 区分包的默认监听器与用户自己的监听器,不让同一个默认监听器被重复注册。
- 针对所支持的每个 PHP、Laravel 版本,持续进行监听器只执行一次的测试。