라라벨 4번째 포스팅에 앞서 DI , IOC 에 대한 개념을 익힐 것을 권장합니다. 아래는 제가 미리 작성해 논 포스팅입니다.
Service Container
라라벨에서 Service Container는 클래스 의존성을 관리하고 의존성 주입(Dependency Injection)을 실행하는 강력한 툴입니다. 대부분 프레임 워크에서 발견할 수 있는 IOC Container 라고 할 수 있습니다.
한 가지 궁금한 점이 생겼습니다. 라라벨의 Request Life Cycle 에 대해서 공부해 보았는데 거기서 서비스 컨테이너는 언제 생성되고 무엇을 하며 언제 종료되는지 궁금하여 소스 코드를 하나씩 추적해보았습니다.
Trace 1. public/index.php
<?php // public/index.php define('LARAVEL_START', microtime(true)); // 1. Composer Autoloading require __DIR__.'/../vendor/autoload.php'; // 2. bootstrap/app.php 에서 $app 인스턴스 생성 $app = require_once __DIR__.'/../bootstrap/app.php';
우선, 라라벨 어플리케이션의 시작점인 index.php를 열어 보겠습니다. 코드는 몇 줄 안되어(라라벨에서 이 파일에서 최소한의 코드를 추가할 것을 권장하고 있습니다.) 라라벨의 작동 원리에 대해 이해하기 쉽습니다. 또한 라라벨 처음 설치 시 주석으로 설명이 적혀 있어 참고하시면 좋을 꺼 같습니다. 일단 앞의 두 줄에 대해 알아보면..
- Composer Autoloading – Composer (PHP 패키지 관리 툴) 를 통해 설치한 /vendor 디렉토리에 존재하는 Composer 구성 요소들을 Autoload 합니다.
- $app 인스턴스 생성 – bootstrap/app.php 에서 $app 인스턴스 생성합니다. require_once 을 사용하여 bootstrap/app.php 파일을 한 번만 로드합니다.
Trace 2. boostrap/app.php
<?php // bootstrap/app.php // 1. Application Instancing $app = new Illuminate\Foundation\Application( $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__) ); // 2. Http Kernel, Console Kernel Binding $app->singleton( Illuminate\Contracts\Http\Kernel::class, App\Http\Kernel::class ); $app->singleton( Illuminate\Contracts\Console\Kernel::class, App\Console\Kernel::class ); // 3. ExceptionHandler Binding $app->singleton( Illuminate\Contracts\Debug\ExceptionHandler::class, App\Exceptions\Handler::class ); // 4. This script returns the application instance return $app;
그림으로 보았던 내용들을 코드로 만나니 뭔가 정리되는 느낌이 들어 마음이 편해집니다. boostrap.php의 스크립트는 4가지 부분으로 나눠 볼 수 있겠습니다. 어플리케이션을 인스턴스화 한 뒤에 Http Kernel, Console Kernel, Exception Handler 를 싱글톤으로 바인딩하고 어플리케이션 인스턴스를 public/index.php 에게 돌려줍니다. 하지만 아직까지 서비스 컨테이너를 찾을 수 없습니다. 좀 더 추적하여 Illuminate\Foundation\Application 소스 코드를 열어 보겠습니다.
Trace 3. Illuminate\Foundation\Application.php
<?php // Illuminate\Foundation\Application.php namespace Illuminate\Foundation; use ... class Application extends Container implements ApplicationContract, HttpKernelInterface { // ... // public function __construct($basePath = null) { if ($basePath) { $this->setBasePath($basePath); } $this->registerBaseBindings(); $this->registerBaseServiceProviders(); $this->registerCoreContainerAliases(); } // ... // }
드디어 찾았습니다!! 라라벨 어플리케이션은 Container 라는 클래스를 상속받아 구현하고 있습니다. 어플리케이션 자체가 서비스 컨테이너인 것을 알 수 있습니다. 추가로 어플리케이션 생성 시 기본적인 설정, ServiceProvider, alias 이런 것들을 등록하는 것을 볼 수 있습니다. 라이플 사이클 흐름대로 따라가니 이해가 더 잘 되는 기분이 듭니다. 아래 그림에서 서비스 컨테이너 개념을 확인할 수 있습니다.

Using Service Container

라라벨의 Service Container 개념에 대해 알아보았습니다. 이제는 사용방법을 알아보겠습니다. 크게 두 가지로 나눌 수 있습니다.
- Binding
- Resolving
Binding
라라벨 서비스 컨테이너에서 바인딩은 의존성 주입에 대한 명세를 직접 해주는 것이라고 볼 수 있습니다. 원하는 구현체가 만들어 질때 어떤 의존성 주입을 해야하는지 알려주는 수동 설정 이라고 보면 됩니다. 아래는 주요한 Binding 에 대한 예제 정리했습니다. 아래 소스 코드 외에 태깅, 조건 바인딩 등은 라라벨 공식 사이트에서 필요할 때 찾아서 사용하시면 됩니다.
<? php // $this->app 를 통해 서비스 컨테이너에 접근할 수 있다!!!!! // 1. Simple Bindings // 'Directory\ClassName' 의 경우에는 생성 시에 'DependencyClassName' 을 주입 $this->app->bind('Directory\ClassName', function ($app) { return new Directory\ClassName($app->make('DependencyClassName')); }); // 2. SingleTon Bindings // 'Directory\ClassName' 의 경우에는 생성 시에 'DependencyClassName' 을 싱글톤으로 주입 $this->app->singleton('Directory\ClassName', function ($app) { return new Directory\ClassName($app->make('DependencyClassName')); }); // 3. Instance 를 생성해서 Bindings // 직접 'Directory\ClassName' 를 생성해서 주입 $api = new Directory\ClassName(new DependencyClassName); $this->app->instance('Directory\ClassName', $api); // 4. 기본 타입 Bindings $value = (int) 1; $this->app->when('Directory\ClassName') ->needs('$variableName') ->give($value); // 5. 인터페이스를 사용하여 알맞는 구현체를 찾아 Bindings $this->app->bind( 'App\Contracts\EventPusher', 'App\Services\RedisEventPusher' );
Resolving
Binding과 상대되는 개념으로 자동 설정이라고 생각하면 됩니다. 객체 사용에 대한 명세 모든 객체들을 바인딩하는 경우는 IOC Container 를 사용하는 이점이 사라지게 됩니다. 라라벨에서 컨트롤러, 이벤트 리스너, 미들웨어 등은 대부분의 의존성 주입은 다음 방법에 따라 이뤄지게 됩니다. 자동 주입은 간단하게 아래 소스 코드 한 줄을 호출하면 해결됩니다.
$userController = $this->app->make('Directory\UserController');
그리고 나서 UserController 생성자에서 Type-Hinting 을 제공하면 Class Reflection를 통해서 라라벨에서는 의존성을 자동으로 해결합니다. UserRepository 가 의존 관계에 있다고 타입 힌팅을 제공하면 해당 namespace를 참고하여 객체를 인스턴스화해서 생성해줍니다. 라라벨 자동 주입과 리플렉션에 대한 개념은 정리가 잘 된 여기를 참조하시면 좋을 것 같습니다.
<?php namespace App\Http\Controllers; use App\Users\Repository as UserRepository; class UserController extends Controller { protected $users; public function __construct(UserRepository $users) { $this->users = $users; } public function show($id) { // } }
TL;DR
- 라라벨에서는 서비스 컨테이너라는 의존성 관리 툴이 있다.
- 소스 코드를 열어 보았는데 Application 객체 자체에 Container 를 상속받아 구현했다.
- 서비스 컨테이너를 사용 시 $this->app 에 접근하여 사용한다.
- 의존성 주입은 대부분은 생성자에 클래스명을 적어주는 방법으로 자동으로 구현하지만 직접 명세가 필요한 경우에는 Binding 관련 메소드를 사용하면 된다.