DIがようやく腑に落ちたので、Laravelサンプルコードと具体例を交えて解説する

# はじめに

プログラミングには「DI」という概念があります。Dependency Injection、「依存性注入」なんて言ったりするのですが、**果たしてこれを初見で理解した方はいるのでしょうか？**
そんなレベルで、このDIは難解です。メリットがよく分からない、という方も非常に多いと思います。

かくいう自分もそうでした。「『単体テストしやすい』ってなんやねん！！」といつもなってました。
これまで触れてきたLaravel、.NET Coreなど、近頃は多くのフレームワークで、だいたいDIの概念はあるのですが、どっちについても「？？？」となってました。
しかし、色々と経験を積むうちに、ようやくそのDIについて、メリットを理解できるまでになりました。今では「これは便利やなー」と思えるぐらいになってます。

そんな訳で今回は、そのDIのメリットについて語っていきます。
なるべく難しい単語は使わず、「なるほど！」と思ってもらえるように頑張ります！
※ちなみに、この記事内での言語は、PHP、Laravelを使用しています。
※サンプルコードは[こちら](https://github.com/hirossyi73/laravel-di-sample)

# 具体的なケース

##  連携できるのは2ヶ月後！？

以下のようなサービスを開発するとします。
- ポケモンずかんを表示するWebシステムを作りたい
- ポケモンの一覧、詳細表示を行う
- 上記の画面や機能をそれぞれ作る
- **ただし、実際にAPI連携可能になるのは、社内監査の都合で、今から2ヶ月後**

※ポケモン一覧は、こちらのAPIを使用して表示します
https://pokeapi.co/

このようなことは、実際の開発現場だとよくあります。
- 契約や課金が必要で、可能な限りギリギリまで課金したくない
- APIの調達に時間がかかる
- API側も開発が必要なプロジェクトで、APIの開発完了がだいぶ先

さあ困りました。今から2ヶ月間は、APIを実行することができません。
2ヶ月間何も出来ないのか…。パット見そのように考えてしまいますが、もう少し考えてみましょう。
**本当に何もできないのか？というと、そういう訳ではないはずです。**

実際のAPIを利用できない期間であっても、以下のような作業は進めることが可能です。 
- クライアント側のデザイン作成 
- 一覧や詳細画面のUI設計 
- コントローラーやモデルの実装

APIを実行して、実際にポケモン一覧を取得することは出来ないですが、その部分はモック(ダミーデータを返却する仕組み)でも作れるはずです。
それ以外の画面デザインや画面制御の部分は、この時点でも作れるはずですね。

ということで、開発者は開発を進めることにしました。具体的には、以下のような流れです。
- 「ポケモン一覧を表示する画面・デザイン」といった、APIと直接関係ない箇所の開発は進める
- 要となる「REST APIを使用したポケモン一覧取得」は、モックを作成する。実際にはAPIを使用せず、固定のポケモンインスタンスの一覧を返却するようにする

それでは、実際に作ってみましょう。以下、サンプルコードです。

#### ポケモンクラスの作成
app/Models/Pokemon.php

```php
<?php
namespace App\Models;

class Pokemon
{
    public $id;
    public $name;

    public function __construct($id, $name)
    {
        $this->id = $id;
        $this->name = $name;
    }
}
```

#### モックリポジトリの作成
app/Repositories/PokemonMockRepository.php

```php
<?php
namespace App\Repositories
;

use App\Models\Pokemon;

class PokemonMockRepository
{
    private $pokemons = [];

    public function __construct()
    {
        $this->pokemons = [
            new Pokemon(1, 'フシギダネ'),
            new Pokemon(2, 'フシギソウ'),
            new Pokemon(3, 'フシギバナ'),
            new Pokemon(4, 'ヒトカゲ'),
            new Pokemon(5, 'リザード'),
            new Pokemon(6, 'リザードン'),

        ];
    }

    public function all()
    {
        return $this->pokemons;
    }

    public function find($id)
    {
        return collect($this->pokemons)->firstWhere('id', $id);
    }
}
```

#### コントローラーの作成
app/Http/Controllers/PokemonController.php

```php
<?php	
namespace App\Http\Controllers;

use App\Repositories\PokemonMockRepository;

class PokemonController extends Controller
{
    private $repository;

    public function __construct()
    {
        $this->repository = new PokemonMockRepository();
    }

    public function index()
    {
        $pokemons = $this->repository->all();
        return view('pokemons.index', compact('pokemons'));
    }

    public function show($id)
    {
        $pokemon = $this->repository->find($id);
        return view('pokemons.show', compact('pokemon'));
    }
}
```

#### ルーティングの作成
route/web.phpに以下を追加する

```php
Route::get('/pokemons', [PokemonController::class, 'index'])->name('pokemons.index');
Route::get('/pokemons/{id}', [PokemonController::class, 'show'])->name('pokemons.show');
```

#### ビューの作成
resources/views/pokemons/index.blade.php

```php
// ※一部のみ記載
<h1>ポケモン図鑑</h1>
<ul>
    @foreach($pokemons as $pokemon)
        <li>
            <a href="{{ route('pokemons.show', $pokemon->id) }}">
                {{ $pokemon->name }}
            </a>
        </li>
    @endforeach
</ul>
```

resources/views/pokemons/show.blade.php

```php
// ※一部のみ記載
<h1>{{ $pokemon->name }}の詳細</h1>
<p>ID: {{ $pokemon->id }}</p>
<p>名前: {{ $pokemon->name }}</p>
<a href="{{ route('pokemons.index') }}">戻る</a>
```

上記のようなコードとなります。これで、ポケモンの一覧を表示できます。
以下のような画面ですね。（びっくりするぐらいシンプルな画面ですが・・・！笑）

![pokemon1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204834/6aa7dab0-75c5-c6a6-7cc4-f14357c731b4.png)
![pokemon2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204834/fb96d4b7-ed9f-b5a4-7c60-9b9a162be55e.png)


### モックと実APIの切り替え 
2ヶ月後、APIの連携が可能になった時点で、**モックから実際のAPIに切り替える方法は非常に簡単です。**
以下のような手順で実装できます。 
1. `PokemonApiRepository` クラスを実装 
2. コントローラーでリポジトリを変更 

#### PokemonApiRepositoryの実装
app/Repositories/PokemonApiRepository.php

```php
<?php

namespace App\Repositories;

use App\Models\Pokemon;
use Illuminate\Support\Facades\Http;

class PokemonApiRepository
{
    private $baseUrl = 'https://pokeapi.co/api/v2/';

    public function all()
    {
        $response = Http::get($this->baseUrl . 'pokemon?limit=20');
        $data = $response->json()['results'];

        return collect($data)->map(function ($item, $index) {
            $id = $index + 1;
            return new Pokemon($id, $item['name']);
        })->all();
    }

    public function find($id)
    {
        $response = Http::get($this->baseUrl . "pokemon/{$id}");
        $data = $response->json();

        return new Pokemon($data['id'], $data['name']);
    }
}
```

#### コントローラーの修正
app/Http/Controllers/PokemonController.php

```php
<?php
namespace App\Http\Controllers;

use App\Repositories\PokemonApiRepository;
// use App\Repositories\PokemonMockRepository; // コメントアウト

class PokemonController extends Controller
{
    private $repository;

    public function __construct()
    {
        // ※重要
        // $this->repository = new PokemonMockRepository(); // コメントアウト
        $this->repository = new PokemonApiRepository(); // 新しいリポジトリを使用
    }

    public function index()
    {
        $pokemons = $this->repository->all();
        return view('pokemons.index', compact('pokemons'));
    }

    public function show($id)
    {
        $pokemon = $this->repository->find($id);
        if ($pokemon) {
            return view('pokemons.show', compact('pokemon'));
        }
        return abort(404);
    }
}
```

これで、最初はMockを使用した開発を行い、途中で実際のAPIに切り替える事ができます。


## DIは外部IFを使用した開発に便利

さて、ここまででも十分開発は可能ですが、このやり方だと以下のような問題点があります。

- 上記コントローラーで「※重要」と書いた箇所。Mockと実APIで、わざわざリポジトリを切り替えないといけない
- 1クラスぐらいであればまだ良いが、複数クラスでMockと実APIを切り替える場合、面倒だし、切り替え忘れなんてことも発生する
- うっかり戻し忘れ、本番環境でMockを使用した画面になってしまう・・・なんてことも

これは非常に問題ですね。どうすればいいでしょうか？
**そう、これを解決するのがDIなんです。**

具体的なコードを見た方が早いですね。
まずは、インタフェースを作成します。

app/Repositories/PokemonRepositoryInterface.php
```php
<?php

namespace App\Repositories;

interface PokemonRepositoryInterface
{
    public function all();
    public function find($id);
}
```
このインタフェースは、**「ポケモンのデータを取得するサービスは、必ず『一覧取得』『詳細取得』の機能を用意してくださいね！」ということを表しています。**

次に、これまで作ったMock・実APIのリポジトリに対し、上記のインタフェースを実装するようにします。
※必要な部分のみ記載しています

app/Repositories/PokemonMockRepository.php
```php
<?php
namespace App\Repositories;

// 以下のように実装
class PokemonMockRepository implements PokemonRepositoryInterface
{
    // その後は省略
}
```

app/Repositories/PokemonApiRepository.php
```php
<?php
namespace App\Repositories;

// 以下のように実装
class PokemonApiRepository implements PokemonRepositoryInterface
{
    // その後は省略
}
```

次に、既存のAppServiceProviderに、以下のコードを追加してください。（サービスプロバイダを新規作成することもできますが、ここでは簡略化します）

app/Providers/AppServiceProvider.php
```php
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
// 以下、追加
use App\Repositories\PokemonRepositoryInterface;
use App\Repositories\PokemonMockRepository;
use App\Repositories\PokemonApiRepository;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        // 以下、追加
        $this->app->bind(PokemonRepositoryInterface::class, function ($app) {
            return config('app.use_mock_pokemon_api')
                ? new PokemonMockRepository()
                : new PokemonApiRepository();
        });
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }
}
```

また、環境変数とconfigを設定します。

```
// .envに以下を追加
USE_MOCK_POKEMON_API=true

// config/app.php
'use_mock_pokemon_api' => env('USE_MOCK_POKEMON_API', false),
```

最後に、コントローラーを以下に修正します。ここがもっとも肝となる箇所になります。

```php
<?php
// コントローラー
namespace App\Http\Controllers;

use App\Repositories\PokemonApiRepository;

class PokemonController extends Controller
{
    // 重要!!!!
    public function __construct(protected PokemonRepositoryInterface $repository)
    {
    }

    public function index()
    {
        $pokemons = $this->repository->all();
        return view('pokemons.index', compact('pokemons'));
    }

    public function show($id)
    {
        $pokemon = $this->repository->find($id);
        if ($pokemon) {
            return view('pokemons.show', compact('pokemon'));
        }
        return abort(404);
    }
}
```

上記の「重要!!!!」と書いていた箇所が肝です。
修正前のコントローラーだと、Mockもしくは実APIのクラスを直接書いていましたが、**ここではPokemonRepositoryInterfaceというリポジトリを指定しています。**
これで、ポケモン一覧・詳細画面が動いてしまいます！実際にMock・実APIの実行クラスはコントローラーに書いていないにもかかわらず、です。すごいですね。

## 解説
可能な限り難しい用語を使用せず、解説をしてみます。

### コントローラーは、「どこから取得するか」はどうでもいい
これまで作成したPokemonControllerでは、ポケモン一覧画面を表示するための実装を行いました（詳細画面もですが、一旦一覧にフォーカスします）。
が、ここでちょっと考えてみましょう。
**このポケモン一覧を「どこから」取得するかどうかは、コントローラー目線だとどうでも良かったりします。**

APIから取得するか？Mockか？データベースか？ローカルに保存しているcsvか？
データの一覧を取得する際に考えられる候補はいくつかありますが、どれを採用したにしても、コントローラー目線だとどうでも良くて、
「どこかから取得したポケモン一覧を画面表示する」という処理が、コントローラーの役割です。

**それを実現するために、PokemonRepositoryInterfaceが登場します。**
このインタフェースでは、allメソッド、findメソッドが用意されています。
コントローラー目線だと、allメソッドを呼び出すことで、「**どこかから**ポケモン一覧を取得」が実現できるわけですね。

イメージ図を作成しました。
PokemonController目線だと、PokemonRepositoryInterfaceさえ呼べていれば良いわけです。その先に何があるかどうかは、コントローラーからしたらどうでもいいわけですね。
![pokemon3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204834/8f508908-edab-1f2a-9b73-638a62c4504a.png)

コントローラーはポケモンの一覧を表示するために「ポケモンのデータがどこから来るのか？」を気にする必要はありません。
このように、データ取得の具体的な実装をコントローラーから切り離すことを「依存関係の分離」と呼びます。
これにより、柔軟にモックや実APIに切り替えることができ、開発やテストが効率的になります。

### じゃあどこから呼び出すか？は、別の箇所で定義
とはいえ、「どこかからデータ一覧を取得する」ものの、 **「じゃあどこから取得するの？」** という点は、指定してあげないといけません。何も指定しないと、コントローラーは困っちゃう（エラーになる）んですね。
それを指定してあげるのは、サービスプロバイダで指定している以下となります。

```php
        $this->app->bind(PokemonRepositoryInterface::class, function ($app) {
            return config('app.use_mock_pokemon_api')
                ? new PokemonMockRepository()
                : new PokemonApiRepository();
        });
```

これはどういう記載かというと、
- PokemonRepositoryInterfaceは、次のうちのどっちかだよ
- configでuse_mock_pokemon_apiが設定されている(.envでUSE_MOCK_POKEMON_API=true)と、Mockを使用するよ
- それ以外は、実APIを使用するよ

というものになります。
なので、.envファイルで設定を書き換えることによって、Mockと実APIを簡単に切り替えることができるわけですね。
このサービスプロバイダの記載を複数書くことによって、複数のコントローラーのMock・実APIをまとめて設定できます。

※ちなみに、Laravelの場合はサービスプロバイダに記載しますが、フレームワークによって記載方法は異なります。それぞれのフレームワークで調べてみてね。

### 自動テストでより便利

自動テストを実装する場合、このDIがより便利となります。
特に、APIなどの外部サービスを絡めた自動テストの場合、何も考えないと問題になるケースがあります。

- 自動テストを行ったタイミングで、大量のAPIリクエストが実行され、API提供者から攻撃とみなされてしまう可能性
- 課金式のAPIの場合、大量のAPIリクエストにより、高額の請求になってしまう可能性
- データ登録があるAPIの場合、自動テストの度にAPIによりデータ登録が行われ、ゴミデータが大量に作成されてしまう可能性

さまざまな問題があります。
こんなときにどうするか？これを解決するのが、まさにDIです。
**自動テストの時には、Mockを使用すれば良いわけなんです。**

サービスプロバイダを、以下のように記載してください。

```php
// AppServiceProvider（一部）
public function register()
    {
        $this->app->bind(PokemonRepositoryInterface::class, function ($app) {
            // テスト環境またはconfigで指定されている場合はMockリポジトリを使用
            if ($app->environment('testing') || config('app.use_mock_pokemon_api')) {
                return new PokemonMockRepository();
            }

            // それ以外の場合は実際のAPIリポジトリを使用
            return new PokemonApiRepository();
        });
    }
```

これで、「configによってMockを指定している場合」または「testingによる実行の場合」、Mockを使用します。
それ以外の場合、実APIを使用します。
切り分けが自動にできて便利ですね。

※ただし、Mockが完全に固定だと、戻りの値も固定となるため、テストの時に不便になる可能性があります。
「パラメータXXXXXが設定されている場合にはエラーとする」みたいに、ちょっとした動的制御はあっても良いかもしれないですね！

# DIが便利なより具体的なケース
DIが便利になるケースはどんなものがあるか？気になる人もいると思います。
具体例をまとめてみました。
※ただし、自分の過去のプロジェクト経験を書くと、色々と支障出るかもしれないので、ここではChatGPTとPerplexity AIに聞いています。
皆様はどちらがお気に入りですか？自分は最近、Perplexity AI（ぱぷちゃん）がお気に入りです

## ChatGPT回答結果

DI（依存性注入）が便利な具体的なケースはいくつかありますが、以下のような状況で特に有用です。

### 1. **テストのしやすさ**
テスト対象のクラスが外部サービスやデータベースなどに依存している場合、DIを使うとその依存部分をモックに置き換えることが容易です。たとえば、支払い処理クラスが外部の決済APIに依存している場合、DIを使ってモックの決済APIを注入すれば、外部APIに実際にアクセスすることなくテストが可能になります。これにより、テスト環境で予期せぬエラーが発生するリスクが減り、テストがより確実に行えます。

### 2. **設定の変更が容易**
特定の設定（例えば、ログレベルや環境ごとのデータベース接続設定）を外部から注入できるようにしておくと、開発・テスト・本番環境ごとに異なる設定を容易に切り替えることができます。DIを使って設定オブジェクトを注入することで、コード変更なしに設定変更ができ、環境依存のコードが減ります。

### 3. **複雑な依存関係の管理**
アプリケーションが大規模になると、依存関係が多くなり管理が困難になります。例えば、あるクラスが複数の他のクラスに依存し、それぞれの依存クラスもさらに別のクラスに依存している場合、DIを使うことで、依存関係の初期化や管理が自動化され、手動でインスタンスを生成する手間が省けます。結果として、コードの可読性が向上し、保守も楽になります。

### 4. **プラグインや拡張の実装**
アプリケーションの機能をプラグイン形式で拡張したい場合、DIを使うと動的に機能を追加できる仕組みを構築しやすくなります。例えば、様々な形式のファイルを処理するプログラムで、新しいファイルフォーマットに対応するパーサーをDIを通して簡単に追加できるようにすることで、プログラム全体を修正せずに機能追加が可能になります。

### 5. **ライブラリの交換が容易**
アプリケーションが特定のライブラリやフレームワークに強く依存している場合、DIを使ってその依存部分を抽象化しておくことで、将来的に別のライブラリに置き換えたいときもコードの修正箇所を最小限に抑えることができます。例えば、メール送信サービスを利用するアプリケーションで、異なるメール送信ライブラリに切り替える必要がある場合、DIを使ってメール送信サービスのインターフェースを注入することで、実装を差し替えるだけで対応できます。

DIは特に**依存関係が複雑化しやすい**システムや、**外部のサービスに依存する処理**が多いアプリケーションで効果を発揮します。

## Perplexity AI

DIが特に便利な具体的なケースをいくつか挙げます：

1. データベースの切り替え：
開発環境ではSQLiteを使用し、本番環境ではPostgreSQLを使用する場合。DIを使えば、データベース接続のインターフェースを定義し、環境に応じて適切な実装を注入できます。

2. 外部APIの利用：
決済サービスのAPIを利用する場合。開発中はモックAPIを使い、本番環境では実際のAPIを使用できます。DIにより、APIクライアントの実装を簡単に切り替えられます。

3. ログ出力の制御：
開発時はコンソールにログを出力し、本番環境ではファイルやデータベースにログを記録したい場合。ログ出力のインターフェースを定義し、環境に応じて適切なロガーを注入できます。

4. 認証システムの変更：
最初はシンプルな認証システムを使用し、後に多要素認証に移行する場合。認証サービスのインターフェースを定義しておけば、新しい認証システムへの移行が容易になります。

5. キャッシュ戦略の変更：
メモリキャッシュからRedisキャッシュに移行する場合。キャッシュのインターフェースを定義しておけば、キャッシュの実装を簡単に切り替えられます。

6. 通知システムの拡張：
最初はメール通知のみだったが、後にSMS通知やプッシュ通知を追加する場合。通知サービスのインターフェースを定義しておけば、新しい通知方法を簡単に追加できます。

7. 設定の管理：
設定をファイルから読み込む方式から、データベースやリモート設定サービスに移行する場合。設定プロバイダのインターフェースを定義しておけば、設定の取得方法を柔軟に変更できます。

8. 機械学習モデルの切り替え：
異なる機械学習モデルを試験的に導入する場合。予測サービスのインターフェースを定義しておけば、異なるモデルを簡単に切り替えて比較できます。

これらのケースでは、DIを使用することで、システムの柔軟性が大幅に向上し、将来の変更や拡張に対応しやすくなります。


# まとめ
DIは、一見複雑に思えるかもしれませんが、開発を進める中での柔軟性を大きく向上させる強力な手法です。
特にAPI連携やテストの際には、モックと実サービスを簡単に切り替えることで、スムーズな開発が可能になります。
ぜひ、DIを活用して、効率的な開発を目指してみてください。

DIを使いこなすと、開発の幅が大きく広がっていきます。
慣れるまではメリットが分かりにくいかもしれないですが、慣れると、よりコードが綺麗に・便利になっていきます。
少しでも、皆様の理解の助けになっていれば嬉しいです！