LaravelでDIを使う


# 目次
Laravelの記事一覧は下記
[PHPフレームワークLaravelの使い方](https://qiita.com/toontoon/items/c4d0371e504c37f6576e)

# Laravelバージョン
動作確認はLaravel Framework 7.19.1で行っています

# 前提条件
[eclipseでLaravel開発環境を構築する。デバッグでブレークポイントをつけて止める。(WindowsもVagrantもdockerも)](https://qiita.com/toontoon/items/76fa7f8845b20776077c)
本記事は上記が完了している前提で書かれています
プロジェクトの作成もapacheの設定も上記で行っています

[Laravelで環境(開発環境と本番環境等)ごとに異なる値を定義する](https://qiita.com/toontoon/items/c1372b34859f94b0225e)
本記事は上記の内容を理解している前提で書かれています

# DIって？
簡単に言うと、作成したクラスをnew演算子を使ってインスタンスを作成せずに、クラスに名前を付けて、付けた名前を使ってインスタンスを呼び出す方法です
そして、名前とクラスのマッピングは独立したファイル内で行います
そうすると、名前とクラスのマッピングファイルを変更するだけで帰ってくるインスタンスのクラスを変えることができます
それの何がいいかというと、ユーティリティクラスやデータベースアクセスクラスなどをテスト時だけダミーの張りぼてクラスに差し替えてテストを実行できるのです

# サービスクラス作成
(1) /sample/app/Servicesフォルダ作成
(2) /sample/app/Services/Interfacesフォルダ作成
(3) /sample/app/Services/InterfacesフォルダにSampleService.php作成

```SampleService.php
<?php
namespace App\Services\Interfaces;

interface SampleService
{
    public function __construct($sample1 = 0, $sample2 = 0);

    public function getSample1();
    public function setSample1($sample1);

    public function getSample2();
    public function setSample2($sample1);

}
```

(4) /sample/app/Services/Implフォルダ作成
(5) /sample/app/Services/ImplフォルダにSampleServiceImpl.php作成

```App\Services\Impl\SampleServiceImpl.php
<?php
namespace App\Services\Impl;

use App\Services\Interfaces\SampleService;

class SampleServiceImpl implements SampleService
{
    private $sample1;
    private $sample2;

    public function __construct($sample1 = 0, $sample2 = 0)
    {
        $this->setSample1($sample1);
        $this->setSample2($sample2);
    }

    public function getSample1()
    {
        $this->sample1++;
        return $this->sample1;
    }

    public function setSample1($sample1)
    {
        $this->sample1 = $sample1;
    }

    public function getSample2()
    {
        $this->sample2++;
        $this->sample2++;
        return $this->sample2;
    }

    public function setSample2($sample2)
    {
        $this->sample2 = $sample2;
    }

}
```

(6) /sample/tests/Servicesフォルダ作成
(7) /sample/tests/Services/Implフォルダ作成
(8) /sample/tests/Services/ImplフォルダにSampleServiceImpl.php作成

```Tests\Services\Impl\SampleServiceImpl.php
<?php
namespace Tests\Services\Impl;

use App\Services\Interfaces\SampleService;

class SampleServiceImpl implements SampleService
{
    public function __construct($sample1 = 0, $sample2 = 0)
    {
    }

    public function getSample1()
    {
        return 1;
    }

    public function setSample1($sample1)
    {
    }

    public function getSample2()
    {
        return 2;
    }

    public function setSample2($sample2)
    {
    }

}
```

# DI登録
(1) /sample/app/Providers/DiServiceProvider.php作成

```DiServiceProvider.php
<?php
namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Services\Interfaces\SampleService;

class DiServiceProvider extends ServiceProvider{

    public function register()
    {
        $env = config('app.env');

        // サービスクラスの登録
        $prefix = 'App\\Services\\Impl\\';
        if ($env === 'test') {
            $prefix = 'Tests\\Services\\Impl\\';
        }

        app()->singleton(SampleService::class, $prefix . 'SampleServiceImpl');

    }

    public function boot()
    {
    }

}
```
config('app.env')はconfig/app.php内に定義された配列のenv要素を取得しています
config('app.env')の値によって登録されるクラスが変わるようにしています
app()->singletonで先ほど作成したサービスクラスをDI登録しています
singletonというのは、クラスのインスタンスが1つしか生成されないようにすることです
要は、あるクラスに対してnew演算子を使うのは1回だけ。new演算子を使って作成されたインスタンスはフレームワーク内の変数に格納され、2回目以降の呼び出し時はその変数からインスタンスを取り出し返すというものになります
ただし、動作確認しているLaravel7.1.1では使い方によってはシングルトンにならない場合があります。それは後述します

(2) /sample/config/app.php修正
上記で作成したサービスプロバイダをLaravelに登録します
/sample/config/app.php内に定義されているproviders配列に`App\Providers\DiServiceProvider::class,`を追記する

```app.php
‥‥
    'providers' => [
‥‥
        /*
         * Application Service Providers...
         */
        App\Providers\DiServiceProvider::class,
‥‥
    ],
‥‥
```
ここに記載されたサービスプロバイダ(Illuminate\Support\ServiceProviderをextendsしたクラス)はLaravelが自動的にロードしてくれます

# Controllerにメソッド追加
DI登録したクラスを使ってみましょう
(1) /sample/app/Http/Controllers/SampleController.phpにuse文を追記
`use App\Services\Interfaces\SampleService;`

(2) /sample/app/Http/Controllers/SampleController.phpにdi1メソッド、di2メソッドを追記

```SampleController.php
    public function di1()
    {
        $data = [];

        $sampleService = app()->makeWith(SampleService::class, ['sample1' => 1, 'sample2' => 2]);
        $data['sample1_1'] = $sampleService->getSample1();
        $data['sample1_2'] = $sampleService->getSample2();

        $sampleService = app()->makeWith(SampleService::class);
        $data['sample2_1'] = $sampleService->getSample1();
        $data['sample2_2'] = $sampleService->getSample2();

        $sampleService = app()->makeWith(SampleService::class, ['sample1' => 100, 'sample2' => 200]);
        $data['sample3_1'] = $sampleService->getSample1();
        $data['sample3_2'] = $sampleService->getSample2();

        return view('sample.di', $data);
    }

    public function di2(SampleService $sampleService)
    {
        $data = [];

        $sampleService->setSample1(1);
        $sampleService->setSample2(2);
        $data['sample1_1'] = $sampleService->getSample1();
        $data['sample1_2'] = $sampleService->getSample2();

        $sampleService = app()->makeWith(SampleService::class);
        $data['sample2_1'] = $sampleService->getSample1();
        $data['sample2_2'] = $sampleService->getSample2();

        $sampleService = app()->makeWith(SampleService::class, ['sample1' => 100, 'sample2' => 200]);
        $data['sample3_1'] = $sampleService->getSample1();
        $data['sample3_2'] = $sampleService->getSample2();


        return view('sample.di', $data);
    }

```
上記を見て分かる通り、makeWithを実行してDI登録したクラスを呼び出すこともできますし、
メソッドの引数を型宣言(タイプヒンティング)することでも呼び出すことができます
makeWithでは第二引数を使ってDI登録したクラスのコンストラクタに引数を渡すことができます
先ほどDI登録したサービスプロバイダ作成のところで述べましたが、
動作確認しているLaravel7.1.1では使い方によってはシングルトンにならない場合があります
それはmakeWithの第二引数に渡した値が、phpのempty関数に与えた結果falseの場合です
makeWithの第二引数に['sample1' => xxx, 'sample2' => xxx]のように配列を渡しているとシングルトンにならずに新しいインスタンスが作成され返されます。コンストラクタの引数が与えられているからですね

(3) /sample/routes/web.phpに下記を追記
`Route::get('sample/di1', 'SampleController@di1');`
`Route::get('sample/di2', 'SampleController@di2');`

# viewの作成
(1) /sample/resources/views/sample/di.blade.phpファイル作成

```di.blade.php
<html>
    <head>
        <title>sample</title>
    </head>
    <body>
        <div>{{$sample1_1}}</div>
        <div>{{$sample1_2}}</div>
        <div>{{$sample2_1}}</div>
        <div>{{$sample2_2}}</div>
        <div>{{$sample3_1}}</div>
        <div>{{$sample3_2}}</div>
    </body>
</html>
```

# 動作確認
`http://localhost/laravelSample/sample/di1`

実行結果

```
2
4
1
2
101
202
```

Controllerにメソッド追加したところで説明したようにmakeWithに第二引数を与えているとシングルトンにならないことがわかります

`http://localhost/laravelSample/sample/di2`

実行結果

```
2
4
3
6
101
202
```

Controllerにメソッド追加したところで説明したようにmakeWithに第二引数を与えないとシングルトンになっていることがわかります

/sample/.envのAPP_ENV=localをAPP_ENV=testに変えて実行してみましょう

`http://localhost/laravelSample/sample/di1`

実行結果

```
1
2
1
2
1
2
```

`http://localhost/laravelSample/sample/di2`

実行結果

```
1
2
1
2
1
2
```

DI登録されたクラスが変わったことがわかります

/sample/.envのAPP_ENVをlocalに戻しておきましょう