[Laravel6.x]依存性の注入(DI)について ~備忘録~

##はじめに
この記事はプログラミング初学者による備忘録用の記事であり、また、少しでも他の初学者のお役に立てればと思い書いています。

今回は、依存性の注入(DI)について色々と学んだので、備忘録としてまとめておきたいと思います。

間違いなどがございましたら、ご指摘のほどよろしくお願い致します。

##"依存性の注入(DI)"とは
依存性の注入とは、**対象となるオブジェクトaの外部から「オブジェクトaで必要な、依存関係にあるオブジェクトbのインスタンス」を「注入」することです**。

しかし、オブジェクトの依存関係を外部から設定しようとすると、オブジェクトを組み立てるのが大変面倒です。
そこで、そんな面倒を避ける為に実装されているのが、Laravelの**[サービスコンテナ](https://readouble.com/laravel/6.x/ja/container.html)**と呼ばれる機能であり、依存性の注入(DI)を簡単に扱うことができます。

サービスコンテナを利用した依存性の注入を実行する為に、Laravelでは主に下記3つの手段を取ることができます。

・サービスコンテナにクラスを登録する
・コンストラクタインジェクションを利用する
・メソッドインジェクションを利用する

クラス（ControllerやServiceなど）のコンストラクタやアクションメソッドに引数が定義されている場合、その引数に設定されたクラスのインスタンスを必要に応じて用意してくれます。


##サービスコンテナにクラスを登録する(結合の定義)
Laravelでは自動注入の機能が備わっていますが、サービスコンテナがインスタンスを注入する際のロジックは、お好みで定義(結合)することができます。
[結合](https://readouble.com/laravel/6.x/ja/container.html#binding)の定義は、下記のように``ServiceProviderのregisterメソッド``に記述します。

``注意:``
基本的にインターフェイスに依存していないのであれば、サービスコンテナでクラスを結合する必要はありません。サービスコンテナにオブジェクトを結合する方法を指示する必要はなく、コンストラクタインジェクションやメソッドインジェクションのように``PHPのReflection``を使用してそのオブジェクトを自動的に依存解決します。

```ServiceProvider.php
public function register(): void
{
//シンプルな結合
$this->app->bind('HelpSpot\API', function ($app) {
    return new \HelpSpot\API($app->make('HttpClient'));
});

//シングルトン結合
$this->app->singleton('HelpSpot\API', function ($app) {
    return new \HelpSpot\API($app->make('HttpClient'));
});
}
```
上記の例のように、結合の定義方法は主に上の2つです。(他にもインスタンス結合やプリミティブ結合が存在するので、詳細は[リンク先](https://readouble.com/laravel/6.x/ja/container.html#binding-basics)をお読み下さい)

###プロバイダの登録
上記のように、サービスプロバイダに結合を定義した後は、``config/app.php``設定ファイルのサービスプロバイダの名前をリストしてある**providers配列**に追加する必要があります。

```config/app.php
'providers' => [
    // Other Service Providers

    App\Providers\ServiceProvider::class,
],
```

###・補足
**・シンプルな結合**
サービスプロバイダの中では、``$this->appプロパティ``によりサービスコンテナへアクセスでき、``bindメソッド``を利用して登録したいクラス名かインターフェイス名と、クラスのインスタンスを返す「クロージャ」を引数として渡すことで結合を登録できます。

**・シングルトン結合**
``singletonメソッド``は、クラスやインターフェイスが一度だけ依存解決されるようにサービスコンテナに登録します。一度シングルトン結合が解決されるとそれ以降、この結合が参照されるたびにサービスコンテナは同じオブジェクトインスタンスを返します。

**・インターフェースなども結合のキーにできる**
結合のキーは、自動的に依存性を注入させる為に「タイプヒントに指定できるクラスの**完全修飾名**」である必要があります。
つまり、完全修飾名を指定できるのであれば、インターフェースや抽象クラスを結合のキーにすることも可能です。



##コンストラクタインジェクション
コンストラクタインジェクションとは、**クラス内のコンストラクタの引数でタイプヒントを使い別のクラスを指定する方法**のことを指します。
下記の例を見てください。コンストラクタの引数にタイプヒントされた引数が存在することを確認できると思います。このタイプヒントされた引数が重要となります。

```ExampleController.php
class ExampleController extends Controller
{
    private $example

    public function __construct(Example $example) //重要
    {
        //プロパティとして持っておく
        $this->example = $example;
    }

    public function update()
    {
        ...
    }
}
```
``コンストラクタインジェクションにおけるサービスコンテナの役割``

・ルートに紐づいたコントローラのコンストラクタに**クラスorインターフェースがタイプヒントされた引数**が存在する場合、指定したクラスorインターフェースのインスタンスを自動的に生成しコンストラクタの引数に注入します。

``補足``
コンストラクタインジェクションを利用する場合、Laravelでは各クラスを生成する前に``PHPのReflection``という機能を用いて対象クラスのコンストラクタの引数を確認します。

Reflectionでは、パラメータのタイプヒンティングを確認できるので、コンストラクタの引数にタイプヒントで指定したクラスを、対象のクラスに入れる必要があることがわかります。
Laravelはこれを把握して、コンストラクタの引数にタイプヒントがあれば自動的に依存性の注入を行ってくれます。

``インターフェースの場合``
インターフェースの場合、インターフェースからはインスタンスが生成されないので予めサービスコンテナに依存解決の結合(ServiceProviderで実装クラスとのひもづけ)を登録しておく必要があります。

```ServiceProvider.php
public function register(): void
{
$this->app()->bind(ExampleInterface::class, ExampleService::class);
//or
$this->app()->bind(ExampleInterface::class,
function($app) {
    return $app->make(ExampleService::class); 
});
}
```
上記のように、サービスプロバイダ内でサービスコンテナに依存解決を指示することで、コンストラクタ内の引数$exampleにはExampleServiceクラスのインスタンスが注入されます。

``::class``はPHPの機能で、完全修飾名を返します。完全修飾名というのは、``\Foo\Bar``のように名前空間も含めた形のクラス名です。

##メソッドインジェクション
メソッドインジェクションとは、**クラスのメソッドの引数にタイプヒントで依存関係にあるクラスを指定する方法**のことを指します。

下記の例を見てください。アクションメソッドにタイプヒントされた引数が存在することを確認できると思います。このタイプヒントされた引数が重要となります。

```ExampleController.php
class ExampleController extends Controller
{
    public function __construct()
    {
        ...
    }

    public function update(Example $example)//重要
    {
        ...
    }
}
```
``メソッドインジェクションにおけるサービスコンテナの役割``

・アクションメソッドの仮引数のタイプヒントがクラスorインターフェースの場合、指定したクラスorインターフェースの依存解決を行い、取得したインスタンスをメソッドの引数に注入します。

※コンストラクタインジェクションと同様に、インターフェースの場合は予めサービスコンテナに依存解決の結合を登録しておく必要があります。

##コンストラクタインジェクションとメソッドインジェクションの使い分け
コンストラクタインジェクションとメソッドインジェクションをどのように使い分けるべきなのか。

私なりの結論としましては、**基本的にはコンストラクタインジェクションを利用し、Controllerクラスに複数のアクションメソッドがある場合は、可読性を高める為にメソッドインジェクションを利用するようにしています。**

##補足

###・依存性とは
依存性の注入を語る上で、プログラムにおける依存性が何なのか理解しておく必要があります。

下記で、依存性が高い例と依存性が低い例をまとめておきます。

``依存性が高い例``
コーヒーメーカーのような、コーヒー専用の機器をプログラムで表してみると「依存性が高い」というものが何なのか理解しやすいと思います。

```.php
class CoffeeMaker{

 protected $capsule; 

 public function __construct() {
        $this->capsule = new CoffeeCapsule;
    }

 public function extract()
  {
  }

}

class CoffeeCapsule
{
}

$coffeeMaker = new CoffeeMaker();
$coffeeMaker->extract(); 
```
CoffeeMakerクラスの中でCoffeeCapsuleクラスのインスタンスを作っています。
もしCoffeeCapsuleクラスが無い場合、new CoffeeCapsule;でエラーが発生します。
このような場合、CoffeeMakerクラスはCoffeeCapsuleクラスに依存していると言えます。

``依存性が低い例``
様々な飲料に対応している業務用ティーサーバーをプログラムで表してみると「依存性が低い」というものが何なのか理解しやすいと思います。

```.php
class TeaServer{

 protected $variousDrinks; 

 public function __construct(VariousDrinks $variousDrinks) 
 {
   $this->variousDrinks = $variousDrinks;
 }

 public function extract()
 {
 }

}

interface VariousDrinks
{
}

class GreenTea implements VariousDrinks
{
}
class OrangeJuice implements VariousDrinks
{
}
```
業務用ティーサーバーの場合、VariousDrinksというインターフェースさえ合っていれば様々な種類のドリンクを飲むことができます。
このような依存性が低い設計が出来ていると、VariousDrinksインターフェースを実装したテスト用クラスを作ることも可能です。

###・サービスコンテナとは
依存性の注入方法について触れる前に、簡単にサービスコンテナについてまとめておきたいと思います。

``サービスコンテナとは``

**クラスの依存関係を管理し、依存注入(DI)を実行するための機能です。**
イメージとしては、サービス(クラス)を入れておく箱だと思って下さい。

``主な機能``

サービスコンテナには、主に2つの機能があります。

**結合:** サービスコンテナにクラスを登録する
**依存解決:** サービスコンテナに登録されたクラスのインスタンスを取り出す

##参考文献
[Laravel6.x サービスコンテナ](https://readouble.com/laravel/6.x/ja/container.html)
[DIコンテナの本当の使いどころ](https://www.ulsystems.co.jp/topics/025)
[Inversion of Control コンテナと Dependency Injection パターン](https://kakutani.com/trans/fowler/injection.html)