【Laravel】サービスコンテナの役割


サービスコンテナの2つの役割

① インスタンスの生成方法をカスタマイズする。
② クラスの依存関係を管理する。

役割① インスタンス化の方法をカスタマイズする

  • サービスコンテナからインスタンスをもらうためには、あらかじめインスタンス生成の方法をサービスコンテナに登録しておく必要がある。
  • インスタンス生成方法を登録することを、bind(結合)という。
  • インスタンスを生成して返すことを、resolve(解決)という。

サービスコンテナのインスタンスを取得する方法

サービスコンテナの実体はIlluminate\Foundation\Applicationクラス

サービスコンテナ(Illuminate\Foundation\Applicationクラス)のインスタンス取得方法は、以下の通り。

//1. app関数
$app1 = app();

//2. Application::getInstanceメソッド
$app2 = \Illuminate\Foundation\Application::getInstance();

//3. Appファサードから取得
$app3 = \App::getInstance();

【tips!】web.phpファイルの中でdd(app())を実行すると、サービスコンテナの中身を簡単に確認することができる。bindを使ってサービスコンテナへの追加を行うと、#bindings: array:bind数 のbindigsの数字が増える。

bind(結合)について

bind(結合)とは、サービスコンテナにインスタンスの生成方法を登録すること。
あらかじめ指示しておくことで、インスタンスの生成方法を自由にカスタマイズすることができる。
※bindの処理は、サービスプロバイダ(App\Providers\AppServiceProvider.php)の
bootメソッド内に書くことが多い。

bindするためのメソッドはいくつかあるが、その内のいくつかを下記に挙げる。

1.bind()メソッド
第1引数にインスタンス生成するクラス、第2引数に生成時の処理(クロージャ)を指定する。

app()->bind(Sample::class, function () {
    return new Sample();
});

2.bindif()メソッド
第1引数にインスタンス生成するクラス、第2引数に生成時の処理(クロージャ)を指定する。
引数で指定されたバインドが存在しない場合のみバインド処理を行い、同名のバインドがすでに存在する場合は、何も行わない。

//同名のバインドが既に存在すると、何も行われない
app()->bindif(Sample::class, function () {
    return new Sample();
});

3.singleton()メソッド
1つのインスタンスのみを生成する。
繰り返しインスタンスを生成しても、同じインスタンスが返される。

app()->singleton(Sample::class, function () {
    return new Sample();
});

resolve(解決)について

resolve(解決)とは、事前に登録された生成方法(bind)に従って、サービスコンテナがインスタンスを生成して返すこと。

サービスコンテナがresolveを行う方法は、大きく分けて1.appヘルパ関数2.makeメソッドの2つ。

1.appヘルパ関数
引数なしで使うと、サービスコンテナのインスタンスを返す。
引数にクラスを指定すると、そのクラスのインスタンスを取得することができる。

2.makeメソッド
引数に対象の文字列を指定すると、指定された文字列にバインドされた処理を実行して、その戻り値を返す。

2'. makeWithメソッド
makeWithメソッドは引数を2つ持っている。第1引数はmakeメソッドと同様。第2引数に、インスタンス作成時にコンストラクタに渡す値を連想配列の形で指定する。連想配列のキーは、値がコンストラクタに渡される引数名になる。

// バインド(結合)
app()->bind(Sample::class, function () {
    return new SampleClass();
})

// 1.appヘルパ関数による解決
$sample1 = app(SampleClass::class);

// 2.makeメソッドによる解決
//   app関数でサービスコンテナを取得し、そのサービスコンテナのmakeメソッドでインスタンス化する
$sample2 = app()->make(SampleClass::class);

// 3.makeWithメソッドによる解決
// SampleClassのコンストラクターに引数を渡す。
$sample3 = app()->makeWith(SampleClass::class, ['id' => $id]);

appヘルパ関数・makeメソッドともに、メソッドが実行されると、引数の文字列にバインド(結合)された処理が実行されて、その戻り値を返す。
※よって、bindメソッドの第1引数(上記のSample::class)と、app()・make()の引数(上記のSampleClass::class)が一致しなければならない。

また、引数に指定した文字列にはバインドされた処理が無い場合も、実際にSampleClassが定義され存在していれば自動でインスタンスが生成される。(new SampleClassするのと同じ)

// bindしていない文字列を引数に指定しても、インスタンスは生成される
$sample4 = app()->make(SampleClass::class);

//下記と同じ挙動になる
$sample4 = new Sampleclass;

役割② クラスの依存関係を管理する

依存性の注入とは

クラスのコンストラクタやアクションメソッドに引数が定義されている場合、サービスコンテナがその引数に設定されたクラスのインスタンスを必要に応じて自動で用意してくれること。

英語では、「Dependency Injection」、略して「DI」と呼ばれる。

DIを行う方法は主に「コンストラクタインジェクション」「メソッドインジェクション」の2つがある。

コンストラクタインジェクション

クラスのコンストラクタの引数のタイプヒンティング(型宣言)で、別のクラスを指定する。
Controller以外のクラス(Serviceクラスなど)に複数のメソッドがある場合、コンストラクタインジェクションを使うと依存関係を一箇所で把握しやすい。

//コンストラクタインジェクション

    private SampleUseCase $sampleUseCase

    public function __construct(SampleUseCase $sampleUseCase)
    {
        $this->sampleUseCase = $sampleUseCase;
    }

//$sampleUseCaseにsampleUseCaseのインスタンスを代入しておくことで、
//sampleUseCaseクラスの他のメソッドでもインスタンスを利用できる。

メソッドインジェクション

クラスのメソッドの引数のタイプヒンティングで別のクラスを指定する。
Contorollerに複数のアクションメソッドがある場合、メソッドインジェクションを使うと、どのメソッドにどの依存があるのか分かりやすい。

//メソッドインジェクション
public function index(SampleUseCase $sampleUseCase)
    {
        $data = $sampleUseCase->data();
    }

//引数$sampleUseCaseに、SampleUseCaseクラスのインスタンスが渡される.
//$sampleUseCase->show()とすることで、SampleUseCaseに定義されているdataメソッドを呼び出す。




参考記事

参考文献