Skip to main content
패키지에서 Laravel 서비스에 기능을 추가할 때, 사용되기 전까지는 인스턴스를 만들지 않으면서도 이미 만들어진 경우에는 설정을 반영하고 싶을 때가 있습니다. 서비스 프로바이더의 callAfterResolving()은 이 두 가지를 조합하기 위한 protected 메서드입니다. 이 페이지에서는 Laravel Framework v13.35.0의 구현을 확인하고, Laravel 패키지 개발에서 다룬 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()과 대상 클래스의 설계를 확인합니다. 서비스를 실제로 지연 로딩하는 프로바이더의 요건은 지연 서비스 프로바이더를 참고하세요. 훅을 사용하는 것과 그 훅을 등록하는 프로바이더 자체를 지연 로딩하는 것은 별개입니다.

업데이트 시 확인할 조합

패키지를 릴리스하기 전에는 일반적인 부팅 순서뿐만 아니라, 다른 프로바이더가 먼저 서비스를 사용한 경우도 검증합니다. 애플리케이션 전체에서 한 번 사용했다는 사실을 정적 플래그로 기록하면, 새 컨테이너나 테스트에서 다시 만들어진 서비스에 대한 설정이 누락될 수 있습니다. 설정의 멱등성은 가능한 한 대상 객체나 등록하는 키 단위로 확보합니다.

관련 페이지

패키지 뷰의 덮어쓰기와 업데이트

loadViewsFrom()이 해석 후 훅을 사용하여 뷰 네임스페이스를 등록하는 예를 확인합니다.

패키지 번역의 덮어쓰기와 업데이트

Translator의 네임스페이스 등록과 이미 로드된 번역의 처리를 확인합니다.

참고한 1차 자료

마지막 수정일 2026년 10월 10일