Laravel サービスコンテナについて - バインドと解決

## サービスコンテナの役割

- Laravel内において、インスタンス管理の役割を担う。
- 各クラスのインスタンスやインスタンスの生成方法を、保持する。
- インスタンスを要求されると、所定の手順にしたがって、インスタンスを生成して返す。
- DI(依存性注入)利用において、注入するインスタンスの生成、また、クラスへの注入も、サービスコンテナが担う。

## サービスコンテナへのインスタンス生成方法の登録(バインド - bind)

- サービスコンテナにインスタンス管理を任せるためには、インスタンスの生成方法を、サービスコンテナに知らせる必要がある。
- 生成方法を登録する処理は、「バインド - bind」と呼ぶ。
- 指定したインスタンスを生成して返すことを、「解決する - resolve」と呼ぶ。

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

- サービスコンテナの操作は、実体である[Illuminate\Foundation\Application](https://github.com/laravel/framework/blob/6.x/src/Illuminate/Foundation/Application.php)クラスのインスタンスに対してメソッドを実行する。
- [Illuminate\Container\Container](https://github.com/laravel/framework/blob/6.x/src/Illuminate/Container/Container.php)クラス(サービスコンテナの機能の多くは、このクラスに実装)を継承している。

```php

// Illuminate\Foundation\Application クラスのインスタンス取得方法
// (下記いずれでも可)

// ①app関数から取得
$app = app();
// ②Application::getInstanceメソッドから取得
$app2 = \Illuminate\Foundation\Application::getInstance();
// ③ファサードから取得
$app3 = \App::getInstance();
```

### バインド方法

#### 1. bind()

- もっとも利用されるメソッド。
- 第1引数に、インスタンス生成するクラス名、第2引数に、生成時の処理(クロージャ)を指定する。
- 第1引数に、連想配列を指定することで、別名でのバインドが可能。

```php

// 基本形
app()->bind(Sample::class, function () {
    return new Sample();
});
$sample = app()->make(Sample::class);

// 別名でのbind
app()->bind([Sample::class => 'sample'], function () {
    return new Sample();
});
$sample2 = app()->make('sample');
```

#### 2. bindif()

- bind()と引数の指定方法は同じ。
- 引数で指定されたバインドが存在しない場合のみ、バインド処理を行う。
- 同名のバインドがすでに存在する場合は、何も行わない。

```php

// ①バインド処理が行われる
app()->bindif(Sample::class, function () {
    return new Sample();
});
// ②同名のバインドがすでに存在するため、何も行われない
app()->bindif(Sample::class, function () {
    return new Sample();
});
```

#### 3. singleton()

- インスタンスを1つのみにする場合に利用。
- インスタンスはキャッシュされ、次からはキャッシュされたインスタンスが返される。

```php

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

// ①インスタンスが生成され、キャッシュされる。
$sample1 = app()->make(Sample::class);

// ②キャッシュされたインスタンスが返される。
$sample2 = app()->make(Sample::class);
```

#### 4. instance()

- 既に生成したインスタンスをサービスコンテナにバインドする。

```php

$sample = new Sample();
// 生成したコンテナをサービスコンテナにバインドする
app()->instance('Sample', $sample);

$sample2 = app()->make('Sample');
```

#### 別の文字列による解決処理のバインド

バインドメソッドの第2引数をクロージャではなく、文字列にすることで、第1引数の文字列を解決するときに、第2引数の文字列を解決して結果を返すことができる。
これを利用することで、解決する名前にインターフェイス名を指定して、指定したインターフェイスの具象クラスのインスタンスを返すことができる。

```php

app()->singleton(SampleInterface::class, Sample::class);

// Sample::classで解決される。
$app = app()->make(SampleInterface::class);
```

### 解決 - resolve

- 指定したインスタンスを生成して返すことを、「解決する - resolve」と呼ぶ。

#### make()

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

```php

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

// makeメソッドによる解決
$app = app()->make(Sample::class);
// app関数による解決
$app2 = app(Sample::class);
```

#### バインドしていない文字列の解決

バインドしていない文字列の場合、解決する文字列がクラス名かつ、具象クラス(インスタンス化できるクラス)であれば、サービスコンテナがそのクラスのコンストラクタを実行してインスタンスを生成する。

```php

class Name
{
    protected $name;

    public function __constructor($name = '')
    {
        $this->name = $name;
    }
}

// 具象クラスが存在するので、解決される。
$app = app()->make(Name::class);

// 第2引数にパラメータを指定して解決することもできる。
$app2 = app()->make(Name::class, ['Taro Yamada']);

```

### 補足

#### バインドの定義場所

アプリケーション内で解決するクラス名のバインド処理は、[app\Providers](https://github.com/laravel/laravel/tree/master/app/Providers)に、`ServiceProvider`クラスを作成して定義するのがよい(既に定義されている[AppServiceProvider](https://github.com/laravel/laravel/blob/master/app/Providers/AppServiceProvider.php)クラス内に定義してもよい)。

## 参考図書

[PHPフレームワーク Laravel Webアプリケーション開発 バージョン5.5 LTS対応](https://www.amazon.co.jp/gp/product/4802611846/ref=ppx_yo_dt_b_asin_title_o02_s00?ie=UTF8&psc=1)