> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# 包的服务解析后钩子

> 解读 Laravel 13 的 callAfterResolving()，讲解如何扩展尚未解析和已解析的服务，以及 singleton、普通绑定和重复注册时的注意事项。

在包中为 Laravel 的服务添加功能时，有时既希望在服务被使用之前不创建实例，又希望在实例已经创建的情况下也能应用配置。服务提供者的 `callAfterResolving()` 就是将这两点结合起来的 protected 方法。

本页基于 Laravel Framework `v13.35.0` 的实现，深入讲解[包开发基础](/zh-CN/advanced/package-development)中介绍的从 `boot()` 进行扩展的方式。它不是等待整个应用程序启动完成的钩子，而是针对指定服务解析的钩子。

## 未解析则等待，已解析则立即执行

`ServiceProvider::callAfterResolving()` 会执行以下两个步骤：

1. 将回调注册到容器的 `afterResolving()`。
2. 如果 `resolved()` 为 true，则通过 `make()` 获取实例，并立即调用回调。

在未解析的情况下，该方法本身不会对目标执行 `make()`。在常规解析中，容器会先构建对象、应用 extender、调用 `resolving` 回调，然后再调用 `afterResolving` 回调。

```mermaid theme={null}
flowchart TD
    A["在 boot() 中调用 callAfterResolving()"] --> B["注册到 afterResolving"]
    B --> C{"目标已 resolved()?"}
    C -->|否| D["当前不创建目标"]
    D --> E["之后目标被解析"]
    E --> F["执行已注册的回调"]
    C -->|是| G["通过 make() 获取目标"]
    G --> H["立即执行回调"]
```

| 方式 | 注册时尚未解析 | 注册时已解析的 singleton |
| - | - | - |
| 通过 `$this->app->make()` 直接配置 | 立即创建 | 配置现有实例 |
| `$this->app->afterResolving()` | 等待之后的解析 | 仅注册不会配置现有实例 |
| `$this->callAfterResolving()` | 等待之后的解析 | 同时立即配置现有实例 |

在常规的 singleton 获取中，容器会提前返回已保存的实例，因此并不是每次获取都会触发 `afterResolving`。需要注意的是，如果只是注册 `afterResolving()`，那么对注册之前已创建的 singleton 的配置就会被遗漏。

## 向验证 Factory 添加规则

作为提供包专属字符串规则的示例，下面在 `validator` 解析后注册规则。官方的 `ValidationServiceProvider` 将该键注册为 singleton，并且该提供者本身支持延迟加载。

```php theme={null}
<?php

namespace Acme\Courier;

use Illuminate\Support\ServiceProvider;
use Illuminate\Validation\Factory;

class CourierServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        $this->callAfterResolving('validator', function (Factory $factory): void {
            $factory->extend(
                'courier_tracking_code',
                static function ($attribute, $value, $parameters, $validator): bool {
                    return is_string($value)
                        && preg_match('/\ACR-[0-9]{8}\z/', $value) === 1;
                },
                'The :attribute must be a valid courier tracking code.',
            );
        });
    }
}
```

在使用该包的应用程序中，可以与常规规则组合使用。

```php theme={null}
$request->validate([
    'tracking_code' => ['required', 'string', 'courier_tracking_code'],
]);
```

此示例中的 `extend()` 是 `Illuminate\Validation\Factory` 的规则注册 API，与后文介绍的容器 `extend()` 是不同的方法。常规的自定义规则在值为空等情况下可能不会执行，因此必填性要通过 `required` 指定。关于规则本身的详细设计，请参阅[自定义验证规则](/zh-CN/advanced/custom-validation-rules)。

`Factory::extend()` 会覆盖同名规则的数组元素，因此像本例这样重复注册相同的处理也不会增加条目。不过，为了避免覆盖其他包的规则，请为公开的规则名添加包专属的前缀。

<Info>
  回调在 Factory 解析时执行。规则的验证闭包则在之后 Validator 验证目标值时才会执行。注册钩子时并不会验证输入值。
</Info>

## 统一目标键与已解析判定

在容器的常规解析事件中，不仅会选择与注册键匹配的回调，还会选择与所获取对象类型匹配的回调。另一方面，`callAfterResolving()` 立即使用的 `resolved($name)` 会针对别名规范化后的键，检查是否存在已解析标记或已保存的实例。它并不会搜索所有已创建的对象。

因此，即使某个接口或类已经被解析，使用另一个键调用 `resolved()` 也不一定为 true。请确认目标服务的实际绑定和 alias，并使用相同的键测试已解析的情况。上面的示例指定的是官方提供者注册的 `validator`。

此外，`resolved()` 的判定也包含"过去曾被解析过"的情况，并不意味着当前实例一定仍然存在。

## 并非保证"只执行一次"的 API

`callAfterResolving()` 会保留已注册的回调。即使已经立即执行过，将来对象被重新解析时，已注册的回调仍会执行。而且，再次调用该方法时会追加新的回调。

尤其需要注意已经解析过的普通非共享绑定。在所确认的实现中，流程如下：

1. 注册新的回调。
2. 由于 `resolved()` 为 true，调用 `make()`。
3. `make()` 创建新对象，并在其解析事件中执行回调。
4. 对 `make()` 返回的同一对象，再立即执行一次回调。

在这条路径中，本次注册的回调会对同一个对象应用两次。请不要将仅针对 singleton 获取而设计的方案直接套用到普通绑定上。

<Warning>
  请不要在回调中发送邮件、调用外部 API、进行计费或无条件添加监听器等。应将其用途限定为以幂等方式向服务应用配置，并确保重复注册或重新解析时不会产生重复的副作用。
</Warning>

## 替换对象请使用其他 API

`afterResolving` 回调的返回值不会被用于替换容器返回的对象。如果想返回装饰器来替换服务本身，请考虑使用容器的 `extend()`。该 API 的闭包约定返回修改后的服务。

同样，`callAfterResolving()` 也不是用来更新已保存在其他对象中的所有依赖的机制。如果目的是在重新绑定时更新依赖，请查阅官方文档中的 `rebinding()` 并确认目标类的设计。

关于真正延迟加载服务的提供者所需满足的条件，请参阅 [DeferrableProvider](/zh-CN/advanced/deferred-provider)。使用钩子与将注册该钩子的提供者本身设为延迟加载是两回事。

## 更新时需要确认的组合

在发布包之前，不仅要验证常规的启动顺序，还要验证其他提供者先使用了该服务的情况。

| 情况 | 需要确认的内容 |
| - | - |
| 在目标未解析的状态下注册钩子 | 仅注册不会创建目标，首次解析时配置生效 |
| 先解析 singleton 再注册 | 配置会立即应用到同一实例 |
| 通过常规 `make()` 再次获取已配置的 singleton | 返回同一实例，解析回调不会重复执行 |
| 对过去已解析的普通绑定进行注册 | 即使注册时的 `make()` 与显式调用共应用两次也不会出错 |
| 多次注册钩子或重新创建实例 | 相同的规则、监听器、路径等不会意外累积 |
| 更新支持的 Laravel 版本 | 重新确认绑定键、共享性以及钩子的执行路径 |

如果使用静态标记记录整个应用程序中只使用过一次，那么在新容器或测试中重新创建的服务可能会遗漏配置。请尽可能以目标对象或所注册键为单位来保证配置的幂等性。

## 相关页面

<Columns cols={2}>
  <Card title="包视图的覆盖与更新" icon="eye" href="/zh-CN/advanced/package-views">
    了解 loadViewsFrom() 如何使用解析后钩子注册视图命名空间的示例。
  </Card>

  <Card title="包翻译的覆盖与更新" icon="language" href="/zh-CN/advanced/package-translations">
    了解 Translator 的命名空间注册以及已加载翻译的处理方式。
  </Card>
</Columns>

## 参考的一手资料

* [Laravel 官方文档：包开发](https://github.com/laravel/docs/blob/13.x/packages.md)
* [Laravel 官方文档：Container events、Extending bindings、Rebinding](https://github.com/laravel/docs/blob/13.x/container.md#container-events)
* [Laravel 官方文档：自定义验证规则](https://github.com/laravel/docs/blob/13.x/validation.md#custom-validation-rules)
* [ServiceProvider：callAfterResolving() 及其在资源注册中的使用](https://github.com/laravel/framework/blob/v13.35.0/src/Illuminate/Support/ServiceProvider.php)
* [Container：resolved()、resolve()、afterResolving()](https://github.com/laravel/framework/blob/v13.35.0/src/Illuminate/Container/Container.php)
* [ValidationServiceProvider：validator 的 singleton 注册](https://github.com/laravel/framework/blob/v13.35.0/src/Illuminate/Validation/ValidationServiceProvider.php)
* [Validation Factory：通过 extend() 注册规则](https://github.com/laravel/framework/blob/v13.35.0/src/Illuminate/Validation/Factory.php)


## Related topics

- [Laravel 包开发](/zh-CN/advanced/package-development.md)
- [进阶主题](/zh-CN/advanced/index.md)
- [服务容器](/zh-CN/service-container.md)
- [错误处理](/zh-CN/error-handling.md)
- [控制器](/zh-CN/controllers.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.