메인 콘텐츠로 건너뛰기
이용 애플리케이션이 선택한 서비스 구현을 그대로 둔 채, 패키지에서 처리를 추가하고 싶을 때가 있습니다. 컨테이너의 extend()를 사용하면 원래의 생성 처리를 재정의하지 않고, 해석된 서비스를 데코레이터로 감쌀 수 있습니다. 이 페이지는 Laravel 공식 문서의 기본 브랜치 13.x와, 2026년 11월 9일에 확인한 Framework 최신 릴리스 v13.40.0의 공식 구현·테스트를 참조합니다. 서비스 해석 후 훅과의 차이에 더해, 등록하는 시점에 인스턴스가 저장되어 있는지 여부에 따라 달라지는 유지 범위를 다룹니다.

설정 추가와 객체 교체를 구분하기

extend()의 콜백은 대상 서비스와 컨테이너를 받습니다. 같은 객체를 설정해서 반환해도 되고, 새 데코레이터를 반환해도 됩니다. return을 잊으면 이후의 해석 처리에 null이 전달됩니다.
여기서 다루는 것은 Illuminate\Container\Container::extend()입니다. Cache나 Mail 등의 Manager가 가진, 드라이버 이름과 생성 처리를 등록하는 extend()와는 다른 API입니다.

같은 계약을 구현하는 데코레이터 만들기

원래 구현을 교체하지 않고 감싸기

설명을 위해 메시지를 포맷하는 다음 계약을 src/MessageFormatter.php에 둡니다. 아래 클래스들도 각각 같은 이름의 PHP 파일을 src/에 만든다고 가정합니다.
데코레이트하기 전의 구현은 입력을 그대로 반환합니다.
데코레이터는 같은 인터페이스를 구현하고, 전달받은 서비스에 처리를 위임합니다. 이 예에서는 위임 대상의 반환값에 패키지 고유의 접두사를 붙입니다.
이용 측이 구체 클래스 PlainMessageFormatter를 타입으로 지정하고 있으면, 같은 인터페이스를 구현했을 뿐인 데코레이터로는 대체할 수 없습니다. 공개하는 의존 대상을 MessageFormatter로 통일하고, 인수·반환값·예외 등의 계약을 유지합니다. 동적인 메서드 전달만으로는 PHP 인터페이스 구현을 대신할 수 없습니다.

프로바이더에서 데코레이션 등록하기

데코레이션을 제공하는 src/PrefixServiceProvider.php는 생성 방법을 덮어쓰지 않고, 컨테이너에 확장 등록만 합니다. 프로바이더 로딩은 패키지 자동 감지의 내부 구조를 참고하세요.
콜백의 두 번째 인수로 컨테이너도 전달되지만, 이 예에서는 필요하지 않으므로 선언하지 않았습니다. 대상 서비스는 인수 $formatter를 사용합니다. 같은 키를 콜백 안에서 다시 make()하면 해석이 순환하는 원인이 됩니다. 이용 애플리케이션의 AppServiceProvider::register() 등에서 원래 구현을 바인딩합니다.
extend()는 대상의 bind()보다 먼저 등록할 수도 있습니다. 대상이 아직 해석되지 않았고 저장된 인스턴스도 없다면, 등록만으로 원래 서비스를 생성하지 않습니다. 공식 테스트에서도 지연 생성과 바인딩 전 등록이 검증되어 있습니다. 그 후 app(MessageFormatter::class)->format('발송했습니다.')는 [Acme] 발송했습니다.를 반환합니다. 원래 구현을 다른 MessageFormatter 구현으로 변경해도, 그 구현을 감싸는 구조입니다.
첫 해석보다 먼저 등록하는 것을 기본으로 하세요. 다른 프로바이더가 먼저 서비스를 사용할 수 있으므로, 단순히 boot()로 옮기는 것만으로는 등록 전 해석을 막을 수 없습니다. 또한 확장 등록만 담당하는 프로바이더를 안이하게 지연 프로바이더로 만들면, 등록 자체의 시작 조건이 없어질 수 있습니다.

적용 대상과 데코레이션 순서 이해하기

일반적인 해석에서는 컨테이너가 원래 서비스를 생성하고, 등록 순서대로 extender의 반환값으로 교체한 뒤, 공유 인스턴스로 저장하고 해석 후 훅을 호출합니다. A, B 순서로 등록하면 결과는 B(A(원래 서비스))입니다. 이용 측의 메서드 호출은 바깥쪽 B부터 시작하므로, 등록 순서와 처리 시작 순서를 혼동하지 마세요. 여러 패키지의 데코레이션 순서에 의존하는 경우에는 이용 애플리케이션에서 순서를 명시하고 조합을 테스트합니다. extender는 컨테이너가 alias를 정규화한 해석 키에 대해 적용됩니다. MessageFormatter::class에 대한 등록이, PlainMessageFormatter::class를 직접 해석한 경우에 타입이 일치한다는 이유만으로 적용되지는 않습니다. 인터페이스를 통한 이용으로 통일합니다.

저장된 인스턴스에 대한 데코레이션은 유지 방식이 다르다

v13.40.0의 Container::extend()는 대상 인스턴스가 컨테이너 안에 저장되어 있는지 여부로 분기합니다. 마지막 행이 중요합니다. 해석된 singleton을 데코레이트한 뒤 forgetInstance()하거나 같은 키를 다시 바인딩해도, 그 자리에서만 적용된 데코레이션은 다음 생성 시에 다시 적용되지 않습니다. 첫 해석 전에 extender 배열에 등록한 경우와는 다릅니다. scoped()도 저장된 인스턴스를 사용하므로, 스코프 도중에 늦게 등록한 데코레이션을 다음 스코프에도 남는 등록이라고 생각하지 마세요. 패키지 서비스의 라이프사이클 설계와 함께 확인합니다. 또한 나중에 instance()로 객체를 직접 저장하면, 일반 생성 시에 extender를 적용하는 루프를 거치지 않습니다. 테스트에서의 교체도 포함해, “등록되어 있으니 전달한 실체도 자동으로 데코레이트된다”고 할 수는 없습니다.

이미 배포된 참조는 자동으로 교체되지 않는다

이 예처럼 새 래퍼를 반환하는 경우, 컨테이너의 저장값이 바뀌어도 다른 서비스의 프로퍼티에 이미 저장된 원래 객체는 바뀌지 않습니다. rebinding() 알림은 등록된 콜백에 새 서비스를 전달하는 구조이며, 모든 의존 대상을 찾아 다시 쓰는 것이 아닙니다. 또한 rebinding()에 의한 알림 콜백 등록도 항상 지연으로 끝나는 것은 아닙니다. 대상이 바인딩되어 있으면 등록 시에도 make()한 결과를 반환합니다. 과거에 해석한 일반 바인딩에 extend()하는 경로에서도, rebound 콜백이 있으면 알림을 위해 make()될 수 있습니다. 초기화나 부작용의 타이밍을 일률적으로 “다음 이용 시”라고 단정하지 마세요.

이중 데코레이션과 상태 이월 피하기

extend()는 같은 클로저나 같은 데코레이터의 등록을 자동으로 중복 제거하지 않습니다. 이 프로바이더의 register()를 같은 컨테이너에 두 번 적용하면, 접두사도 두 번 붙을 수 있습니다.
  • 데코레이션 등록은 프로바이더의 정해진 시작 경로에 모으고, 요청이나 잡마다 반복하지 않는다.
  • 중복 방지를 위해 모든 컨테이너가 공유하는 단일 정적 플래그를 사용하지 않는다. 테스트나 새 애플리케이션에서 필요한 등록이 빠진다.
  • singleton의 데코레이션도 같은 공유 기간 동안 유지된다. 현재 사용자나 요청 등을 래퍼에 고정하지 않는다.
  • forgetExtenders()는 등록 배열을 지울 뿐이며, 생성된 래퍼를 원래대로 되돌리는 작업이 아니다.
이 예에서는 출력 문자열을 변경하고 있습니다. 접두사·위임 횟수·예외 처리도 패키지의 공개 동작이 되므로, 단순히 “구현의 내부 변경”으로 취급하지 말고 호환성을 확인합니다.

같은 컨테이너로 등록 시기 테스트하기

다음 PHPUnit 예시는 위 클래스들을 패키지의 autoload로 불러올 수 있다는 전제입니다. 프로바이더의 register()를 Container에 직접 적용하고, HTTP나 외부 서비스를 사용하지 않고 데코레이션의 유지 범위를 확인합니다. 자동 감지나 애플리케이션 전체의 시작은 별도로 Orchestra Testbench로 Laravel 패키지를 테스트하기에서 검증하세요.
마지막 테스트는 늦은 등록을 권장하는 것이 아니라, 확인한 버전의 경계를 명시하는 것입니다. forgetInstance()만을 데코레이션의 해제·재등록 API로 설계하지 마세요. 릴리스 전에는 여러 extender의 순서, 중복 등록, instance()에 의한 교체, 이용 측이 이미 보유한 참조도 확인합니다. 지원 Laravel 버전을 늘릴 때는 해석 키와 타입 계약에 더해, extend()의 분기와 공식 테스트를 다시 읽습니다.

관련 페이지

패키지의 서비스 해석 후 훅

객체를 교체하지 않고 설정하는 callAfterResolving()과 적용 횟수의 주의점을 확인합니다.

패키지 서비스의 라이프사이클 설계

bind·singleton·scoped와, 생성된 서비스가 보유하는 의존 관계를 확인합니다.

참조한 1차 정보

마지막 수정일 2026년 11월 9일