> ## 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 패키지 개발](/ko/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`로 지정합니다. 규칙 자체의 자세한 설계는 [커스텀 검증 규칙](/ko/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()`과 대상 클래스의 설계를 확인합니다.

서비스를 실제로 지연 로딩하는 프로바이더의 요건은 [지연 서비스 프로바이더](/ko/advanced/deferred-provider)를 참고하세요. 훅을 사용하는 것과 그 훅을 등록하는 프로바이더 자체를 지연 로딩하는 것은 별개입니다.

## 업데이트 시 확인할 조합

패키지를 릴리스하기 전에는 일반적인 부팅 순서뿐만 아니라, 다른 프로바이더가 먼저 서비스를 사용한 경우도 검증합니다.

| 케이스 | 확인할 내용 |
| - | - |
| 대상이 해석되지 않은 상태에서 훅을 등록 | 등록만으로는 대상을 생성하지 않고, 첫 해석 시 설정이 반영된다 |
| singleton을 먼저 해석한 후 등록 | 같은 인스턴스에 그 자리에서 설정이 반영된다 |
| 설정된 singleton을 일반 `make()`로 다시 조회 | 같은 인스턴스가 반환되고, 해석 콜백이 중복 실행되지 않는다 |
| 과거에 해석된 일반 바인딩에 등록 | 등록 시의 `make()`와 명시적 호출로 두 번 적용되어도 문제가 없다 |
| 여러 번의 훅 등록이나 인스턴스 재생성 | 같은 규칙·리스너·경로 등이 의도치 않게 누적되지 않는다 |
| 지원하는 Laravel 버전을 업데이트 | 바인딩 키, 공유 여부, 훅의 실행 경로를 다시 확인한다 |

애플리케이션 전체에서 한 번 사용했다는 사실을 정적 플래그로 기록하면, 새 컨테이너나 테스트에서 다시 만들어진 서비스에 대한 설정이 누락될 수 있습니다. 설정의 멱등성은 가능한 한 대상 객체나 등록하는 키 단위로 확보합니다.

## 관련 페이지

<Columns cols={2}>
  <Card title="패키지 뷰의 덮어쓰기와 업데이트" icon="eye" href="/ko/advanced/package-views">
    loadViewsFrom()이 해석 후 훅을 사용하여 뷰 네임스페이스를 등록하는 예를 확인합니다.
  </Card>

  <Card title="패키지 번역의 덮어쓰기와 업데이트" icon="language" href="/ko/advanced/package-translations">
    Translator의 네임스페이스 등록과 이미 로드된 번역의 처리를 확인합니다.
  </Card>
</Columns>

## 참고한 1차 자료

* [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 패키지 개발](/ko/advanced/package-development.md)
- [고급 주제](/ko/advanced/index.md)
- [패키지의 정적 해석 (PHPStan / Larastan)](/ko/advanced/package-static-analysis.md)
- [지연 서비스 프로바이더](/ko/advanced/deferred-provider.md)
- [패키지 자동 감지의 내부 구조](/ko/advanced/package-discovery.md)


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