> ## 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` 的實作，深入探討[Laravel 套件開發](/zh-TW/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 取得流程中，容器會提前 return 已儲存的實例，因此並不是每次取得都會觸發 `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-TW/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-TW/advanced/deferred-provider)。使用掛鉤，與將註冊該掛鉤的提供者本身設為延遲載入，是兩回事。

## 更新時應確認的組合

發布套件前，除了一般的啟動順序外，也要驗證其他提供者先使用了該服務的情況。

| 情況 | 確認事項 |
| - | - |
| 在目標尚未解析時註冊掛鉤 | 僅註冊不會建立目標，並在首次解析時套用設定 |
| 先解析 singleton 再註冊 | 設定會當場套用到同一個實例 |
| 以一般的 `make()` 再次取得已設定的 singleton | 回傳同一個實例，且解析回呼不會重複執行 |
| 對過去已解析的一般綁定註冊 | 即使因註冊時的 `make()` 與明確呼叫而套用兩次也不會出錯 |
| 多次註冊掛鉤或重新產生實例 | 相同的規則、監聽器、路徑等不會非預期地累積 |
| 更新支援的 Laravel 版本 | 重新確認綁定鍵、共用性與掛鉤的執行路徑 |

若以靜態旗標記錄在整個應用程式中只使用過一次，可能會導致新的容器或在測試中重新建立的服務缺少設定。設定的冪等性應盡可能以目標物件或註冊鍵為單位來確保。

## 相關頁面

<Columns cols={2}>
  <Card title="套件 View 的覆寫與更新" icon="eye" href="/zh-TW/advanced/package-views">
    確認 loadViewsFrom() 使用解析後掛鉤註冊 View 命名空間的範例。
  </Card>

  <Card title="套件翻譯的覆寫與更新" icon="language" href="/zh-TW/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

- [進階主題](/zh-TW/advanced/index.md)
- [Laravel 套件開發](/zh-TW/advanced/package-development.md)
- [Lottery 類別](/zh-TW/advanced/lottery.md)
- [延遲服務提供者](/zh-TW/advanced/deferred-provider.md)
- [套件 Migration 的公開與更新](/zh-TW/advanced/package-migrations.md)


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