Skip to main content
패키지를 일반 HTTP 요청뿐 아니라 Octane이나 큐 워커에서도 사용할 수 있게 하려면, 서비스가 어느 기간 동안, 무엇을 보유해도 되는지를 정해야 합니다. 요청별 데이터를 singleton에 저장하면 다른 처리에서도 오래된 데이터를 참조할 가능성이 있습니다. 이 페이지에서는 Laravel Framework v13.36.0의 컨테이너와 큐 워커, 그리고 Octane의 구현을 확인하고, 서비스 컨테이너의 기본 등록 방법을 패키지의 장기 유지보수와 연결합니다. scoped()를 선택하는 것만으로 객체 간의 참조나 정적 상태가 모두 리셋되는 것은 아닙니다.

등록 방법은 서비스의 보유 기간으로 선택한다

이것은 일반적인 해석을 비교한 표입니다. 파라미터를 명시한 해석이나 contextual binding 등 특수한 경로는 별도로 확인하세요. bind()라도 조회한 서비스를 다른 singleton의 프로퍼티에 저장하면 그 참조는 남습니다. 등록 방법은 컨테이너의 생성·재사용 규칙이며, 사용자가 보유한 객체까지 강제로 다시 만드는 규칙이 아닙니다.

scoped는 공유 등록과 제거 대상 기록을 조합한다

확인한 Container::scoped()는 다음 두 가지를 수행합니다.
  1. 등록 키를 scopedInstances에 기록합니다.
  2. singleton()을 호출하여 공유 바인딩으로 등록합니다.
인스턴스 자체는 singleton과 같은 instances에 저장됩니다. forgetScopedInstances()는 기록된 키에 대해 저장된 인스턴스를 unset()합니다. 바인딩의 생성 처리는 남아 있으므로 다음 일반 해석에서 새 객체를 만들 수 있습니다. 이 처리는 오래된 객체의 프로퍼티를 초기화하거나 다른 객체가 가진 참조를 바꾸지 않습니다. 참조가 남아 있는 오래된 객체는 계속 사용될 수 있습니다.

경계를 만드는 것은 실행 환경

컨테이너 자체가 HTTP 요청이나 잡의 시작을 감시하는 것은 아닙니다.
  • 큐 워커: QueueServiceProvider가 스코프를 리셋하는 콜백을 Worker에 전달합니다. Worker::daemon()은 다음 잡을 가져오기 전 루프에서 이를 호출하며, 콜백 안에서 forgetScopedInstances()와 Facade의 해석된 인스턴스 정리 등을 수행합니다.
  • Octane: 공식 기본 설정에서는 OperationTerminated에 FlushTemporaryContainerInstances를 등록합니다. 이 리스너가 forgetScopedInstances()를 호출합니다.
  • 자체 상주 커맨드나 루프: 단순히 make()를 반복해도 경계가 되지 않습니다. 실행 기반 쪽에서 라이프사이클을 설계해야 합니다.
일반적인 애플리케이션 처리나 패키지의 boot()에서 임의로 forgetScopedInstances()를 호출하지 마세요. 컨테이너 전체의 scoped 서비스에 영향을 줍니다. 또한 동기 실행을 포함한 모든 잡 경로가 상주 Worker와 같은 리셋 경로를 거치는 것은 아닙니다.

처리 안에서 공유하는 상태를 scoped로 만든다

배송 패키지가 하나의 요청·잡 안에서 배송지 식별자를 공유하고 싶은 예를 생각해 봅시다. HTTP Request를 직접 보유하지 않고 필요한 값만 저장합니다.
패키지의 서비스 프로바이더에서는 생성 시 사용자의 상태를 읽지 않고 빈 context를 등록합니다.
사용하는 애플리케이션의 컨트롤러나 잡의 handle()에서 입력 검증과 권한 확인을 마친 식별자를 설정합니다. 같은 라이프사이클에서 이 타입을 해석하는 다른 서비스는 같은 context를 참조할 수 있습니다.
이 context는 인가 기능이 아닙니다. 식별자를 설정할 수 있다는 것과 해당 배송지에 대한 접근이 허용되었다는 것은 별개입니다. 또한 scoped()는 데이터베이스나 외부 캐시로의 영속화도 아닙니다. 다음 잡에 전달할 값은 잡의 데이터로 명시적으로 전달합니다.
Octane에서는 서비스 프로바이더의 register()와 boot()가 워커의 초기 기동 시에 실행됩니다. 현재 사용자나 배송지를 boot()에서 context에 저장하는 설계로 만들지 마세요. 처리별 값은 처리가 시작된 후에 설정합니다.

singleton에서 scoped로의 참조는 자동으로 갱신되지 않는다

다음 등록은 context만 scoped로 만들어도 의도대로 동작하지 않습니다.
DeliveryService가 생성자에서 context를 프로퍼티에 저장한다면, 처음 생성한 context를 계속 보유합니다. 컨테이너에서 context의 저장을 제거해도 이미 생성된 DeliveryService의 프로퍼티는 바뀌지 않습니다. 대책은 서비스의 책임에 맞게 선택합니다.
  1. 처리별로 필요한 값을 메서드 인수로 전달한다. 상태를 공유할 필요가 없다면 이것이 가장 단순합니다.
  2. 보유하는 쪽도 scoped로 만든다. DeliveryService와 context를 모두 처리 단위로 다시 만듭니다.
  3. 사용 시점에 해석하는 resolver를 전달한다. singleton을 유지해야 하는 경우에는 현재 컨테이너에서 필요한 서비스를 그때마다 가져옵니다.
세 번째 예에서는 오래된 context를 클로저에 캡처하지 않고 호출 시점에 해석합니다.
이 예의 DeliveryService는 생성자에서 Closure를 받아 작업할 때마다 호출하는 API로 변경해야 합니다. 생성자 안에서 resolver를 한 번만 호출하고 그 결과를 저장하면 해결되지 않습니다. 이는 공식 Octane 문서의 container resolver를 패키지 고유의 context에 응용한 설계 예입니다.

scoped로 사라지지 않는 상태도 확인한다

scoped()는 다음 상태를 자동으로 제거하지 않습니다.
  • 패키지 고유의 정적 프로퍼티에 추가한 값
  • 다른 singleton이나 전역 변수에 저장한 context에 대한 참조
  • singleton 내부에 보유한 “현재 사용자”, “현재 배송지”
  • 자체 Facade·캐시 계층이 보유한 참조
표준 실행 기반에 의한 리셋과 패키지 고유 상태의 리셋은 구별합니다. 처리별 상태를 보유하는 곳을 줄이는 편이 모든 실행 환경에서 자체 정리 처리를 유지하는 것보다 단순합니다.

같은 컨테이너에서 경계를 넘는 테스트 작성하기

HTTP 테스트가 매번 새 애플리케이션을 만드는 것만으로는 상주 프로세스에서 같은 컨테이너를 계속 사용하는 문제를 놓칠 수 있습니다. 먼저 컨테이너 단독으로 같은 처리 안에서의 공유와 경계 이후의 재생성을 검증합니다. 다음 테스트는 PHPUnit 형식입니다. 첫 번째 테스트는 공식 Container 테스트와 마찬가지로 forgetScopedInstances()를 사용하여, Worker 전체를 기동하지 않고 경계의 작용을 확인합니다.
두 번째는 참조가 남는 사양을 기록하는 테스트입니다. 실제 패키지에는 채택한 대책으로 새 context나 값이 사용되는지 검증하는 테스트도 추가합니다. 컨테이너 단독 테스트는 Octane의 sandbox나 큐 워커의 실제 실행 경로를 검증하는 것이 아닙니다. 지원을 표명하는 실행 환경에서 연속 처리를 수행하는 통합 테스트도 필요합니다. 등록 방법이나 서비스 구현을 변경한 배포에서는 상주 프로세스를 다시 로드하여 새 코드를 적용합니다.

관련 페이지

패키지의 서비스 해석 후 훅

afterResolving과 공유 인스턴스 조회, 재해석 시 설정의 차이를 확인합니다.

Laravel Octane

Request나 컨테이너의 보유, 상주 프로세스 운영상의 주의점을 확인합니다.

참고한 1차 자료

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