[Laravel 기초] 4. Service Container

라라벨 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를 열어 보겠습니다. 코드는 몇 줄 안되어(라라벨에서 이 파일에서 최소한의 코드를 추가할 것을 권장하고 있습니다.) 라라벨의 작동 원리에 대해 이해하기 쉽습니다. 또한 라라벨 처음 설치 시 주석으로 설명이 적혀 있어 참고하시면 좋을 꺼 같습니다. 일단 앞의 두 줄에 대해 알아보면..

  1. Composer Autoloading – Composer (PHP 패키지 관리 툴) 를 통해 설치한 /vendor 디렉토리에 존재하는 Composer 구성 요소들을 Autoload 합니다.
  2. $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 이런 것들을 등록하는 것을 볼 수 있습니다. 라이플 사이클 흐름대로 따라가니 이해가 더 잘 되는 기분이 듭니다. 아래 그림에서 서비스 컨테이너 개념을 확인할 수 있습니다.

Service Container

Using Service Container

Using Laravel 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 관련 메소드를 사용하면 된다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다