callAfterResolving() 就是将这两点结合起来的 protected 方法。
本页基于 Laravel Framework v13.35.0 的实现,深入讲解包开发基础中介绍的从 boot() 进行扩展的方式。它不是等待整个应用程序启动完成的钩子,而是针对指定服务解析的钩子。
未解析则等待,已解析则立即执行
ServiceProvider::callAfterResolving() 会执行以下两个步骤:
- 将回调注册到容器的
afterResolving()。 - 如果
resolved()为 true,则通过make()获取实例,并立即调用回调。
make()。在常规解析中,容器会先构建对象、应用 extender、调用 resolving 回调,然后再调用 afterResolving 回调。
在常规的 singleton 获取中,容器会提前返回已保存的实例,因此并不是每次获取都会触发
afterResolving。需要注意的是,如果只是注册 afterResolving(),那么对注册之前已创建的 singleton 的配置就会被遗漏。
向验证 Factory 添加规则
作为提供包专属字符串规则的示例,下面在validator 解析后注册规则。官方的 ValidationServiceProvider 将该键注册为 singleton,并且该提供者本身支持延迟加载。
extend() 是 Illuminate\Validation\Factory 的规则注册 API,与后文介绍的容器 extend() 是不同的方法。常规的自定义规则在值为空等情况下可能不会执行,因此必填性要通过 required 指定。关于规则本身的详细设计,请参阅自定义验证规则。
Factory::extend() 会覆盖同名规则的数组元素,因此像本例这样重复注册相同的处理也不会增加条目。不过,为了避免覆盖其他包的规则,请为公开的规则名添加包专属的前缀。
回调在 Factory 解析时执行。规则的验证闭包则在之后 Validator 验证目标值时才会执行。注册钩子时并不会验证输入值。
统一目标键与已解析判定
在容器的常规解析事件中,不仅会选择与注册键匹配的回调,还会选择与所获取对象类型匹配的回调。另一方面,callAfterResolving() 立即使用的 resolved($name) 会针对别名规范化后的键,检查是否存在已解析标记或已保存的实例。它并不会搜索所有已创建的对象。
因此,即使某个接口或类已经被解析,使用另一个键调用 resolved() 也不一定为 true。请确认目标服务的实际绑定和 alias,并使用相同的键测试已解析的情况。上面的示例指定的是官方提供者注册的 validator。
此外,resolved() 的判定也包含”过去曾被解析过”的情况,并不意味着当前实例一定仍然存在。
并非保证”只执行一次”的 API
callAfterResolving() 会保留已注册的回调。即使已经立即执行过,将来对象被重新解析时,已注册的回调仍会执行。而且,再次调用该方法时会追加新的回调。
尤其需要注意已经解析过的普通非共享绑定。在所确认的实现中,流程如下:
- 注册新的回调。
- 由于
resolved()为 true,调用make()。 make()创建新对象,并在其解析事件中执行回调。- 对
make()返回的同一对象,再立即执行一次回调。
替换对象请使用其他 API
afterResolving 回调的返回值不会被用于替换容器返回的对象。如果想返回装饰器来替换服务本身,请考虑使用容器的 extend()。该 API 的闭包约定返回修改后的服务。
同样,callAfterResolving() 也不是用来更新已保存在其他对象中的所有依赖的机制。如果目的是在重新绑定时更新依赖,请查阅官方文档中的 rebinding() 并确认目标类的设计。
关于真正延迟加载服务的提供者所需满足的条件,请参阅 DeferrableProvider。使用钩子与将注册该钩子的提供者本身设为延迟加载是两回事。
更新时需要确认的组合
在发布包之前,不仅要验证常规的启动顺序,还要验证其他提供者先使用了该服务的情况。
如果使用静态标记记录整个应用程序中只使用过一次,那么在新容器或测试中重新创建的服务可能会遗漏配置。请尽可能以目标对象或所注册键为单位来保证配置的幂等性。
相关页面
包视图的覆盖与更新
了解 loadViewsFrom() 如何使用解析后钩子注册视图命名空间的示例。
包翻译的覆盖与更新
了解 Translator 的命名空间注册以及已加载翻译的处理方式。