2024년 6월 17일 월요일

Laravel : ServiceProvider & Service Container

Laravel : ServiceProvider & Service Container

Laravel : Service Provider & Container

공식문서에서 정의한 내용을 보자.
https://laravel.kr/docs/8.x/providers

쉽게 가보자.
서비스 컨테이너와 서비스 프로바이더에서 서비스라는 단어를 빼고 생각해 보자.

컨테이너와 프로바이더의 관계가 된다.
컨테이너는 말 그대로 뭔가 들어있는 우리가 항만에서 볼수있는 그 거대한 컨테이너박스를 의미한다.
프로바이더는 제공자라는 의미로 뭔가를 공급하는 사람을 뜻한다.

우리가 만드는 어플리케이션을 하나의 생산품이라고 생각해보자. 이때 우리는 자주 사용하는 부품이 여러 공정에서 필요한데 이를 매번, 매공정마다 담당자가 제조사에 전화해서 달라고 하면 담당자마다 다른 곳에 전화하는 실수를 한다든지 잘못 주문한다든지 하게된다.
이럴때 우리는 프로바이더에게 해당 부품의 공급을 의뢰해서 컨테이너 단위로 물건을 요청할수있다. 이렇게 하면 프로바이더에게 요청하는 담당자를 통해서 부품을 공급받게되어 빠르고 안정적으로 제품을 생산할수 있는 환경이 된다.

서비스 컨테이너와 서비스 프로바이더도 같은 원리이다.

Laravel에서 Service Provider 는 애플리케이션이 기동되는 순간(이를 라라벨에서는 부트스트래핑이라고 부른다.) 필요한 프로바이더들을 등록하고 프로바이더가 무엇을 제공할건지 서비스 컨테이너 내용을 등록하면 우리가 개발하는 프로그램의 어디에서든지 자유롭게 컨테이너의 내용을 꺼내서 활용할수 있도록 해준다.

Service Provider

서비스 프로바이더(서비스 공급자) 는 위에서 설명했듯이 제품을 공급해줄 담당자를 지정한다.

config/app.php 에 보면 providers 항목이 있고, Laravel 프레임워크에서 필요한 프로바이더와 애플리케이션에서 필요한 프로바이더를 미리 등록해 놓은 것이 보인다.
물론 우리가 만든 프로바이더도 등록이 가능하다.

추가하는 provider 는 ServiceProvider 를 상속받는데 register 와 boot 함수를 오버라이드 한다.

register : 객체를 서비스 컨테이너에 바인딩(연결하다) 한다.
boot : laravel이 부트스트래핑할때 호출된다.

여기서 착각하면 안되는 것이, 각 프로바이더마다 register->boot 순으로 실행되는게 아니라, 모든 프로바이더의 register가 종료된후에 모든 프로바이더의 boot 가 실행된다는 것이다.

register()

register 함수에서는 외부에서 프로바이더에게 요청시에 대답해줄 컨테이너정보를 등록한다. 즉, 컨테이너를 가지는게 아니라 보내줄 컨테이너를 지정한다.
컨테이너를 연결할때, singleton 과 bind, instance, when, tag 등의 연결방법이 있다.

singleton : 말그대로 외부에서 여러번 요청해도 하나의 똑같은 컨테이너만 돌려준다.
bind : 컨테이너 요청때마다 새로운 컨테이너 만들어서 돌려준다.
instance : 간단하게 컨테이너를 등록할수있다. 테스트코드 작성시에 Mock용 컨테이너 등록시에 유용하다.
when : 의존관계에서 다른 의존관계의 컨테이너가 필요할때 사용한다.
tag : 여러개를 그룹화 해서 하나의 컨테이너 키이름으로 불릴수있게 해준다.

1.make 로 나만의 프로바이더를 만든다.

php artisan make:provider MyProductServiceProvider

2.생성된 /app/Providers/MyProductServiceProvider.php 가 생성되었고 register()/boot() 빈함수 두개가 있다.

public function register()  
{  
    $this->app->singleton('myutility', function($app){  
       return new MyUtility();   
    });  
}

3.MyUtility 클래스가 없으므로 app/MyUtils/MyUtility.php 를 폴더생성과 함께 만들자.

class MyUtility  
{  
    public function exampleMethod($value): string  
  {  
        return strtoupper($value);  
    }        
}

4.config/app.php 파일에 우리가 만든 프로바이더도 등록해보자.

confit/app.php 
'providers' => [
....
App\Providers\MyProductServiceProvider::class,
]

5.프로바이더를 통해서 컨테이너가 등록이 되었고 어디서든지app(‘myutility’)라는 호출명령어로 MyUtility가 불려지게 된다. 컨트롤러에서 사용해보자.

class ExampleController extends Controller  
{  
    protected MyUtility $myUtility;  
  
    public function __construct()  
    {  
        $this->myUtility = app('myutility');  
    }  
  
    public function index()  
    {  
        $value = 'hello world';  
        return $this->myUtility->exampleMethod($value); // "HELLO WORLD" 반환  
  }  
}

6.라우터에 등록해서 확인해 보면 대문자로 HELLO WORLD 가 출력되는것을 확인할수 있다.

boot()

boot 는 다른 프로바이더의 register가 모두 끝난후에 호출된다.

1.위에서 만들었던 MyproductServiceProvider.php 의 boot 함수에 내용을 추가해보자.

public function boot()  
{  
    //  
  view()->composer('welcome', function($view){  
        return $view->with('helloworld', app('myutility')->exampleMethod('hey! there'));  
    });       
}

2.welcome.blade.php 에서 helloworld 변수를 사용해보자 .

<h1> {{ $helloworld }} </h1>

3.뷰화면에 hey! there 가 출력된다.

Service Provider implements DeferrableProvider

deferred. 말그대로 지연 시킨다는 건데 무엇을 지연시키느냐면 register 를 지연 시킨다는 말이다.
부트스트래핑과저에서 모든 프로바이더에 대해 register에서 사용할 컨테이너를 만들 준비를 해둔다. 이렇게 하면 프로바이더가 많아질수록 애플리케이션 초기기동이 늦어질수있는데, 프로바이더 에 요청할때 비로소 컨테이너만들 준비를 하도록 일단 지연 시키는게 deferrer 이다.

기존 서비스 프로바이더 선언에 DeferrableProvider 인터페이스를 추가하고 provides 함수를 오버라이드해서 사용할 컨테이너를 적으면 된다.

class MyProductServiceProvider extends ServiceProvider  implements  DeferrableProvider


public  function  provides()
{
return  [MyUtility::class];
}

사용할때는 동일하다.

Facade

서비스 프로바이더를 사용해는 것도 좋지만 app()함수를 통해 만드는것은 더 직관적이라 어떤 서비스 프로바이더인지 알아보기 어려울수도 있다.
Facade(겉으로, 표면적인) 으로 등록하면 서비스 프로바이더 호출 이름만으로도 쉽게 알아볼수 있게 해준다.

1.Facade파일을 생성하자. getFacadeAccessor 함수에 위의 과정에서 등록했던 ‘myutility’ 키 이름을 지정하면 자동으로 서비스 프로바이더를 불러준다.

class MyUtilityFacade extends Facade  
    {  
    protected static function getFacadeAccessor()  
    {  
        return 'myutility'; // 서비스 컨테이너에 등록된 키  
  }  
}

2.config/app.php 에 aliases 로 등록 하자.

'MyUtilityFacade' => App\Providers\MyProductServiceProvider::class,

3.컨트롤러에서 적었던 서비스 프로바이드 관련 정보를 삭제하고, MyUtirilityFacade 를 이용해 보자.

use App\Facades\MyUtilityFacade;  
  
class ExampleController extends Controller  
{  
  
    public function index()  
    {  
        $value = 'Hello World';  
        return MyUtilityFacade::exampleMethod($value); // "HELLO WORLD" 반환  
  }  
}

0 comments:

댓글 쓰기