Skip to main content
在包中为 Laravel 的服务添加功能时,有时既希望在服务被使用之前不创建实例,又希望在实例已经创建的情况下也能应用配置。服务提供者的 callAfterResolving() 就是将这两点结合起来的 protected 方法。 本页基于 Laravel Framework v13.35.0 的实现,深入讲解包开发基础中介绍的从 boot() 进行扩展的方式。它不是等待整个应用程序启动完成的钩子,而是针对指定服务解析的钩子。

未解析则等待,已解析则立即执行

ServiceProvider::callAfterResolving() 会执行以下两个步骤:
  1. 将回调注册到容器的 afterResolving()。
  2. 如果 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() 会保留已注册的回调。即使已经立即执行过,将来对象被重新解析时,已注册的回调仍会执行。而且,再次调用该方法时会追加新的回调。 尤其需要注意已经解析过的普通非共享绑定。在所确认的实现中,流程如下:
  1. 注册新的回调。
  2. 由于 resolved() 为 true,调用 make()。
  3. make() 创建新对象,并在其解析事件中执行回调。
  4. 对 make() 返回的同一对象,再立即执行一次回调。
在这条路径中,本次注册的回调会对同一个对象应用两次。请不要将仅针对 singleton 获取而设计的方案直接套用到普通绑定上。
请不要在回调中发送邮件、调用外部 API、进行计费或无条件添加监听器等。应将其用途限定为以幂等方式向服务应用配置,并确保重复注册或重新解析时不会产生重复的副作用。

替换对象请使用其他 API

afterResolving 回调的返回值不会被用于替换容器返回的对象。如果想返回装饰器来替换服务本身,请考虑使用容器的 extend()。该 API 的闭包约定返回修改后的服务。 同样,callAfterResolving() 也不是用来更新已保存在其他对象中的所有依赖的机制。如果目的是在重新绑定时更新依赖,请查阅官方文档中的 rebinding() 并确认目标类的设计。 关于真正延迟加载服务的提供者所需满足的条件,请参阅 DeferrableProvider。使用钩子与将注册该钩子的提供者本身设为延迟加载是两回事。

更新时需要确认的组合

在发布包之前,不仅要验证常规的启动顺序,还要验证其他提供者先使用了该服务的情况。 如果使用静态标记记录整个应用程序中只使用过一次,那么在新容器或测试中重新创建的服务可能会遗漏配置。请尽可能以目标对象或所注册键为单位来保证配置的幂等性。

相关页面

包视图的覆盖与更新

了解 loadViewsFrom() 如何使用解析后钩子注册视图命名空间的示例。

包翻译的覆盖与更新

了解 Translator 的命名空间注册以及已加载翻译的处理方式。

参考的一手资料

最后修改于 2026年10月10日