서비스 컨테이너란
Laravel의 서비스 컨테이너는 클래스의 의존 관계를 관리하고 의존성 주입을 수행하기 위한 메커니즘입니다. 의존성 주입이란 클래스가 필요로 하는 의존을 생성자나, 경우에 따라서는 세터 메서드를 통해 클래스로 “주입”하는 것을 가리킵니다. 다음 예를 살펴봅시다.PodcastController는 Apple Music 등의 데이터 소스에서 Podcast를 가져와야 합니다. 그래서 Podcast를 가져올 수 있는 서비스를 주입합니다. 서비스를 주입하면 테스트 시 AppleMusic 서비스의 목(더미 구현)을 간단히 교체할 수 있습니다.
서비스 컨테이너를 깊이 이해하는 것은 대규모 Laravel 애플리케이션을 구축하는 데 있어 필수입니다. Laravel 코어 자체에의 기여에도 도움이 됩니다.
제로 컨피그레이션 해결
클래스가 다른 구체 클래스(인터페이스가 아님)에만 의존하고 있는 경우, 컨테이너에게 그 해결 방법을 알려줄 필요가 없습니다. 예를 들어 다음 코드를routes/web.php에 작성한다고 가정합니다.
이 예는 라우트 파일 내에서 클래스를 정의하고 있지만, 이는 데모용입니다. 실제 애플리케이션에서는 서비스 클래스는
app/Services 디렉터리에 정의하세요.Service 클래스를 해결하여 라우트 핸들러에 주입합니다. 설정 파일을 준비하지 않아도 의존성 주입의 혜택을 받을 수 있습니다.
컨트롤러, 이벤트 리스너, 미들웨어 등 Laravel 애플리케이션에서 작성하는 대부분의 클래스는 컨테이너를 통해 자동으로 의존이 주입됩니다.
바인딩
기본적인 바인딩
대부분의 바인딩은 서비스 프로바이더 내에서 등록합니다. 서비스 프로바이더 내에서는$this->app 프로퍼티를 통해 컨테이너에 접근할 수 있습니다.
bind
bind 메서드를 사용하여 클래스 또는 인터페이스명과 클로저를 전달하여 바인딩을 등록합니다.
App 파사드를 사용합니다.
인터페이스에 의존하지 않는 클래스는 컨테이너에 바인딩할 필요가 없습니다. 컨테이너는 리플렉션을 사용하여 이러한 객체를 자동으로 해결할 수 있습니다.
singleton
singleton 메서드는 클래스 또는 인터페이스를 한 번만 해결하도록 바인딩합니다. 한 번 해결된 싱글톤은 이후 컨테이너에 대한 호출에서 같은 인스턴스가 반환됩니다.
singletonIf 메서드를 사용하면 지정된 타입에 대한 바인딩이 아직 등록되어 있지 않은 경우에만 싱글톤 바인딩을 등록할 수 있습니다.
Singleton 어트리뷰트
클래스나 인터페이스에#[Singleton] 어트리뷰트를 붙임으로써도 한 번만 해결되도록 컨테이너에 지시할 수 있습니다.
스코프 있는 싱글톤 바인딩
scoped 메서드는 클래스 또는 인터페이스를 Laravel의 요청/잡 라이프사이클 내에서 한 번만 해결하도록 바인딩합니다. singleton 메서드와 비슷하지만, scoped 메서드로 등록된 인스턴스는 Laravel Octane 워커가 새 요청을 처리할 때나, 큐 워커가 새 잡을 처리할 때 등, Laravel 애플리케이션이 새 “라이프사이클”을 시작할 때마다 파기됩니다.
scopedIf 메서드를 사용하면 지정된 타입에 대한 바인딩이 아직 등록되어 있지 않은 경우에만 스코프 있는 바인딩을 등록할 수 있습니다.
Scoped 어트리뷰트
클래스나 인터페이스에#[Scoped] 어트리뷰트를 붙임으로써도 요청/잡 라이프사이클 내에서 한 번만 해결되도록 컨테이너에 지시할 수 있습니다.
instance
기존 객체 인스턴스를instance 메서드로 컨테이너에 바인딩할 수도 있습니다. 이후 컨테이너에 대한 호출에서는 항상 그 인스턴스가 반환됩니다.
인터페이스를 구현에 바인딩
서비스 컨테이너의 강력한 기능 중 하나는 인터페이스를 특정 구현에 바인딩할 수 있다는 것입니다. 예를 들어EventPusher 인터페이스와 RedisEventPusher 구현이 있다고 가정합시다.
EventPusher의 구현이 필요한 클래스에 RedisEventPusher를 주입하게 됩니다. 이후 생성자에서 EventPusher 인터페이스를 타입힌트하기만 하면 됩니다.
Bind 어트리뷰트
Laravel은 더욱 편리한Bind 어트리뷰트도 제공합니다. 인터페이스에 이 어트리뷰트를 붙이면, 그 인터페이스가 요청되었을 때 어떤 구현을 자동으로 주입할지 Laravel에 알릴 수 있습니다. Bind 어트리뷰트를 사용하는 경우, 서비스 프로바이더에서 추가 등록 처리를 할 필요가 없습니다.
또한 인터페이스에 여러 Bind 어트리뷰트를 배치하여 환경마다 다른 구현을 주입하도록 설정할 수도 있습니다.
BindWhen 어트리뷰트를 사용할 수 있습니다. 클로저에는 컨테이너를 전달할 수 있으며, 바인딩을 적용할 때 true를 반환합니다. Bind와 BindWhen 어트리뷰트는 선언된 순서대로 평가됩니다.
BindWhen 어트리뷰트를 사용하려면 PHP 8.5 이상이 필요합니다.자동 해결 (타입힌트에 의한 DI)
서비스 컨테이너는 컨트롤러, 이벤트 리스너, 미들웨어 등의 클래스를 해결할 때 생성자의 타입힌트를 보고 의존을 자동으로 주입합니다.UserRepository가 인터페이스에 의존하지 않는다면 컨테이너로의 등록은 필요 없습니다. 다음 라우트에 접근하기만 해도 컨테이너가 자동으로 의존을 해결하여 컨트롤러에 주입합니다.
컨테이너에서 해결
make 메서드
make 메서드를 사용하여 컨테이너에서 클래스 인스턴스를 해결할 수 있습니다.
makeWith 메서드를 사용하여 추가 인수를 전달할 수도 있습니다.
자동 주입
실제로는make 메서드를 직접 호출하는 일은 거의 없습니다. 컨테이너가 해결하는 클래스(컨트롤러, 이벤트 리스너, 미들웨어 등)의 생성자에 타입힌트를 추가하기만 하면 컨테이너가 자동으로 주입해 줍니다.
파사드와 컨테이너의 관계
Laravel의 파사드는 컨테이너 내의 객체에 대한 정적 인터페이스를 제공합니다. 예를 들어Cache::get()은 내부적으로 컨테이너에서 Cache 서비스를 가져와 호출하고 있습니다.
생성자 주입의 실전 예
실제 애플리케이션에서의 전형적인 패턴을 살펴봅시다.1
인터페이스 정의
2
구현 클래스 작성
3
서비스 프로바이더에서 바인딩
4
컨트롤러에서 주입 받기
Stripe에서 다른 프로바이더로 전환할 경우에도 바인딩을 한 곳만 변경하면 대응할 수 있습니다.
다음 단계
서비스 프로바이더
서비스 프로바이더를 사용하여 바인딩을 등록하는 방법을 배웁니다.