【Laravel】サービスコンテナ・サービスプロバイダ・ファサード・契約に関する補足資料

# はじめに

Laravel の基礎部分となる非常に重要な4つの概念。基本的にはマニュアルおよびその日本語訳を参照すべし。

- [サービスコンテナ 6.x Laravel](https://readouble.com/laravel/6.x/ja/container.html)
- [サービスプロバイダ 6.x Laravel](https://readouble.com/laravel/6.x/ja/providers.html)
- [ファサード 6.x Laravel](https://readouble.com/laravel/6.x/ja/facades.html)
- [契約 6.x Laravel](https://readouble.com/laravel/6.x/ja/contracts.html)

ここでは（個人的に）それらだけでは不足していると考えられる情報を補うことにする。

# サービスコンテナをサービスプロバイダ以外から利用する方法

サービスプロバイダでは `$this->app->make()` を利用していたが， `$this->app` にアクセスできない場所では代わりに [`resolve()`] または [`app()`] というグローバル関数が利用できる。引数を渡す場合は両者とも全く同じ動きをする。

```php
$repository = resolve(UserRepository::class);
```

```php
$repository = app(UserRepository::class);
```

ではサービスコンテナ自体をサービスプロバイダ以外で取得する場合はどうするか？実は [`app()`] 関数のみ引数無しで使うことができ，これでサービスコンテナを直接取得することができる。 コンテナの [`make()`] 以外のメソッドを呼びたい場合などにはたまに使うことがありそうだ。

```php
$app = app();
```

またコンテナは [`ArrayAccess`] インタフェースを実装しているため，配列形式で解決することもできる。とにかく方法が<del>無駄に</del>何通りもある…

```php

$repository = app()[UserRepository::class];
```

# サービスコンテナに登録されている実装を取得する方法

## コンストラクタインジェクションを利用する方法

```php
class UserController extends Controller
{
    public function __construct(UserRepository $users)
    {
        $this->users = $users;
    }
}
```

クラスの型指定（タイプヒント）を書いて，実装を自動判定させる方法。基本的に，これが使用可能である場合には最優先で使用すべし。

> 一番重要な依存解決方法は、コンテナによる依存解決が行われるクラスのコンストラクタで、シンプルに依存を「タイプヒント」で記述するやりかたです。これはコントローラ、イベントリスナ、キュージョブ、ミドルウェアなどで利用できます。実践でコンテナによりオブジェクトの解決が行われるのは、これが一番多くなります。

「具体的にどのような場合に解決できるか？」という疑問には **サービスコンテナ経由でインスタンスが生成されている場所** と答えられる。例えばこのコントローラの場合には，

```php
$controller = app()->make(UserController::class);
```

などの記述に相当する処理を Laravel フレームワークのコアがやってくれているから，と説明できる。

## ファサードを利用する方法

コンストラクタインジェクションが利用できないが，できるだけ節度を持たせたい，というときにはファサードがおすすめだ。以下は実際に自分のプロジェクトで利用している Elasticsearch クライアントのファサードである。 `Es` という略称で使いやすい形にしている。

```php
<?php

namespace App\Facades;

use App\Services\Elasticsearch\Client;
use Illuminate\Support\Facades\Facade;

class Es extends Facade
{
    public static function getFacadeAccessor(): string
    {
        return Client::class;
    }
}
```

アクセサの名前は基本的に**「実装のクラス名」**または**「契約名（インタフェース名）」**のいずれかでよい。Laravel コアのクラスは `db` `router` `request` など短いエイリアスを当てているものが多いが，自分で実装するクラスはこれで十分だ。

コンストラクタインジェクションが利用できない場所としては以下の場所が筆頭であろう。

- **Eloquent Model**
- **Eloquent Collection**

ファットモデルを目指し，モデルにビジネスロジックをたくさん実装している場合には一考の余地がある。

**なお，リアルタイムファサードは（テスト用途以外で）用いることはおすすめしない。**IDE補完も全く効かないしとにかく混沌を招くことになる。

ちなみに [Laravel IDE Helper] で補完の恩恵を受けるためには，ファサードクラスを明示的に作成することに加えて，グローバル名前空間にエイリアスの登録も必要だ。

```php:config/app.php
return [
    
    /* ... */
    
    'aliases' => [
        'Es' => App\Facades\Es::class,
    ],
];
```

## グローバル関数を利用する方法

上で説明した `resolve()` `app()` を利用する方法である。使用される場所が限定的であるためファサードを作るまでもない，というときにおすすめの方法だ。 

なお， [Laravel IDE Helper] が導入されている PhpStorm 環境の場合には， 

```php
resolve(UserRepository::class)->
```

と入力すると入力補完がしっかり出るようになっている。

## メソッドインジェクションを利用する方法

自作クラスでの利用機会は比較的少なめだが，Laravelコアクラスはそれなりに利用している方法である。

- エンドポイントに対応するコントローラのメソッド
- ジョブの `handle()` メソッド
- コンソールコマンドの `handle()` メソッド

こういった場所では，コンストラクタ以外の任意のメソッドでも依存性注入を利用できる。

```php
class UserController extends Controller
{
    public function show(Request $request, UserRepository $users): User
    {
        return $users->find((int)$request->input('user_id'));
    }
}
```


「具体的にどのような場合に解決できるか？」という疑問には **サービスコンテナ経由でメソッドが実行されている場所** と答えられる。コンテナは [`call()`] というメソッドを持っているため，これを利用する。

```php:Illuminate\Container\Container
/**
 * Call the given Closure / class@method and inject its dependencies.
 *
 * @param  callable|string  $callback
 * @param  array  $parameters
 * @param  string|null  $defaultMethod
 * @return mixed
 */
public function call($callback, array $parameters = [], $defaultMethod = null);
```

例えばこのコントローラの場合には，

```php:UserControllerのインスタンスをコンテナに生成させて，それのshowメソッドをコールする
app()->call(UserController::class . '@show');
```

```php:UserControllerの生成済みインスタンスのshowメソッドをコールする
app()->call([$userController, 'show']);
```

```php:UserControllerの静的なshowメソッドをコールする
app()->call([UserController::class, 'show']);
```

などの記述に相当する処理を Laravel フレームワークのコアがやってくれているから，と説明できる。ちなみに，コンテナで自動的に解決できない引数がある場合，もしくはコンテナで指定されたものをオーバーライドしたい場合には，**引数の名前をキーとする連想配列** を第2引数に渡して利用する。

```php
app()->call(UserController::class . '@show', ['request' => new Request()]);
```

[Laravel IDE Helper]: https://github.com/barryvdh/laravel-ide-helper
[`ArrayAccess`]: http://php.net/manual/ja/class.arrayaccess.php
[`resolve()`]: https://readouble.com/laravel/6.x/ja/helpers.html#method-resolve
[`app()`]: https://readouble.com/laravel/6.x/ja/helpers.html#method-app
[`make()`]: https://github.com/illuminate/container/blob/ec885f4b9ae5be2341a4bf714c96ce2b10c886c5/Container.php#L606-L616
[`call()`]: https://github.com/illuminate/container/blob/ec885f4b9ae5be2341a4bf714c96ce2b10c886c5/Container.php#L580-L591