Laravelの依存性の注入（DI）を具体的にどう使うのか

※この話で利用したLaravelのバージョンは 5.4.36です。

依存性の注入（DI）についてはググればすぐにその基礎知識がわかる。  
[Laravelの公式ドキュメント](https://readouble.com/laravel/5.5/ja/providers.html)を見れば、LaravelがDIの仕組みを持っている事がわかる。  
しかしドキュメントがイマイチわかりにくく、DIを自分で使いたい場合どこに何を書けばいいのか具体的にはよくわからない。

## LaravelのDIの仕組みを具体的に使ってみる
### 手順
1. 入れ替わるクラスの共通メソッド名を定義するインターフェイスを作成する。
2. そのインターフェイスをimplementsした複数のクラスを作成する。これらが環境によって入れ替わる。
3. サービスプロバイダ設定をする。環境ごとの処理分けをここに記述する。
   * サービスプロバイダファイルを新規作成し、/config/app.php に追記する。
   * または既存のサービスプロバイダファイルに追記する。
4. コントローラーで「Illuminate\Foundation\Application」が使用できるようにuseする。
5. `$app->make(サービスプロバイダで名付けた名前)` で依存性注入されたクラスのインスタンスを取り出す。

### 具体例で解説
* 猫クラスがある。  
* ジャンプするというメソッドを持つ。  
* 開発版では3メートルジャンプする。  
* 製品版では6メートルジャンプする。  
* 2種類の猫クラスを、LaravelのDI機能を利用して環境ごとに入れ替える。

#### 1. 入れ替わるクラスの共通メソッド名を定義するインターフェイスを作成する。
/app/CatInterface.php

```php
namespace App;

interface CatInterface
{
    public function jump();
}
```

#### 2. そのインターフェイスをimplementsした複数のクラスを作成する。これらが環境によって入れ替わる。
/app/CatLocal.php

```php
namespace App;

class CatLocal implements CatInterface
{
    public function jump(){
        return 'ローカル猫は3メートルジャンプした';
    }
}
```

/app/CatProduction.php

```php
namespace App;

class CatProduction implements CatInterface
{
    public function jump(){
        return '製品猫は6メートルジャンプした';
    }
}
```

#### 3. サービスプロバイダ設定をする。環境ごとの処理分けをここに記述する。
/app/Providers/CatServiceProvider.php

```php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;

class CatServiceProvider extends ServiceProvider
{
    public function boot()
    {
    }

    public function register()
    {
        //env('APP_ENV') で .env ファイルの「APP_ENV」の値が取れる
        $env = env('APP_ENV');

        switch($env){
            case('production'):
                $this->app->bind('cat', 'App\CatProduction');
                break;

            default:
                $this->app->bind('cat', 'App\CatLocal');
        }
    }
}
```

/config/app.php

```php
// （省略）
    'providers' => [
        // （省略）

        //追加
        App\Providers\CatServiceProvider::class,
    ],
// （以後省略）
```


#### 4. コントローラーで「Illuminate\Foundation\Application」が使用できるようにuseする。
#### 5. `$app->make(サービスプロバイダで名付けた名前)` で依存性注入されたクラスのインスタンスを取り出す。
ちなみにルーティング設定
/routes/web.php

```php
Route::get('/home', 'HomeController@index')->name('home');
```

/app/Http/Controllers/HomeController.php

```php
<?php

namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Foundation\Application as Application; //DI利用のため

class HomeController extends Controller
{
    public function index(Application $app)
    {
        //App\Providers\CatServiceProvider での依存性の注入設定に従ってインスタンス化される
        $cat = $app->make('cat');
        echo($cat->jump());
    }
}
```

Application::getInstance()でもApplicationインスタンスを取得できるようです。

```php
use Illuminate\Foundation\Application;
// （中略）
$app = Application::getInstance();
```


### 実験結果
#### .env の「APP_ENV」の値が「local」のとき
![スクリーンショット 2017-10-12 12.21.27.png](https://qiita-image-store.s3.amazonaws.com/0/159641/948dd927-95c8-78dd-7003-56ec727c0e02.png)


#### .env の「APP_ENV」の値が「production」のとき
![スクリーンショット 2017-10-12 12.21.00.png](https://qiita-image-store.s3.amazonaws.com/0/159641/4fed2bc1-94aa-9c21-1c04-670b90fc063d.png)


## ちょっと解説
自分の理解している範囲での解説です。

* Laravelでは依存性の注入の実現のために「サービスコンテナ」という仕組みを使っているらしい。
* サービスコンテナというのはサービスを入れてまとめる箱のようなものらしい。
* サービスというのは「メールを送信する」「ユーザーを認証する」みたいな個々の機能。
* サービスプロバイダというのは個々の機能を具体的に実現するクラスのこと。
* 「この名前でこのクラスを登録しておくよ」「指定した名前を経由して登録されたクラスを取り出すよ」ということができるようになっている。

### サービスコンテナ
* 公式ドキュメントでの解説では `$this->app` という名前の変数でよく登場する。
* その実態は Illiminate\Foundation\Application クラスのインスタンスである。
* サービスプロバイダファイル内では `$this->app` がいきなり使えるが、コントローラーファイル内ではuseしないと使えない。
* `$this->app->bind(任意の名前, 紐付けられるクラス)` で任意の名前と実際に働くクラスとを紐付けする。
* `$this->app->make(任意の名前)` で取り出す。

## 追記
コメントにてよりスマートなやり方を紹介いただきました。  
su_miさんのコメントをぜひご覧ください。