新卒がLaravelでDIの流れを追う(サービスプロバイダーとコンテナ)


#用語
###DIとは
Dependency Injection。依存を回避しながらオブジェクトを注入するということ。
コンテナという容器に、いろいろなクラスのインスタンスを入れてあげる。アプリではそのコンテナが与えられているので、必要な時に、その中から任意のインスタンスを取り出して使ってあげる。

いったんコンテナという容器にインスタンスを入れる作業をしてやり、その後アプリではコンテナからインスタンスを取り出している。間接的なインスタンスの使用って感じ。こうすると保守がし易い。

###サービスプロバイダー
LaravelでDIするためのクラス。今回のサービスプロバイダーではコンテナの中にクラス作成のためのメソッドを格納して、必要な時にそのメソッドを発動させてインスタンスを作り返すようになっている。


#サービスの登録
###登録を遡る

まずサービスプロバイダーから追っていく。register()でサービスを登録しているようだが、appとbindが謎なのでServiceProviderを追う。
こいつは[laravel自体のコード](https://github.com/laravel/framework/blob/7.x/src/Illuminate/Support/ServiceProvider.php)なので見に行く。

```php:アプリのServiceProvider
use Illuminate\Support\ServiceProvider;

class SampleServiceProvider extends ServiceProvider
{
    public function register() { 
	$this->app->bind('Sample', function($app){
		Return new Sample();
	});
    }
}
```

⬇️
コンストラクタ でappがプロパティとして登録されている。そのappってのは\Illuminate\Contracts\Foundation\Applicationなんだとか。

```php:framework/src/Illuminate/Support/ServiceProvider.php
namespace Illuminate\Support;

abstract class ServiceProvider
{ 
    /**
    * Create a new service provider instance.
    * 
    * @param  \Illuminate\Contracts\Foundation\Application  $app 
    * @return void 
    */ 
    public function __construct($app)
    {
        $this->app = $app;
    }

}
```

⬇️

Containerを継承している。コンテナにサービスをぶち込むというイメージなのだろう。Containerを確認。

```php:framework/src/Illuminate/Foundation/Application.php
class Application extends Container implements ApplicationContract, CachesConfiguration, CachesRoutes, HttpKernelInterface
{
.
.
.
}
```

⬇️
bindメソッドを発見。
いろいろやっていたがcompactを使って、bindingsというプロパティーに\$abstractをキーにして$concreteを格納している。

```php:framework/src/Illuminate/Container/Container.php
class Container implements ArrayAccess, ContainerContract
{
    public function bind($abstract, $concrete = null, $shared = false) {
    .
    .
    .
    $this->bindings[$abstract] = compact('concrete', 'shared');
    .
    .
    .
    }
}
```

###サービスプロバイダに戻って確認

```php
use Illuminate\Support\ServiceProvider;
use App\Sample;

class SampleServiceProvider extends ServiceProvider
{
    public function register() { 
	$this->app->bind('Sample', function($app){
		Return new Sample();
	});
    }
}
```
containerインスタンスのbindingsにSampleというキーで、Sampleクラスのインスタンス作成のためのメソッドがバリューとして登録されることになる。


###登録のイメージ
以下の登録のイメージは自分の想像で書いています。間違っていたらすいません。

コンテナにいろいろなメソッド(必要インスタンスを作成)を登録するために、まず一つ目のサービスプロバイダーのプロパティーにコンテナインスタンスを代入。
⬇️
登録処理
⬇️
一つ目のサービスプロバイダーのコンテナプロパティーを、二つ目のサービスプロバイダーのプロパティーに代入。
⬇️
繰り返し

串カツ(コンテナインスタンス)をいろいろなタレ(サービスプロバイダー)に付けている感じ。
<img width="412" alt="スクリーンショット 2020-07-31 9.05.05.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510817/0c8593f4-aea4-a506-ab67-07bfa91fbcaa.png">


#サービスの使用
###使用を遡る
サービスを使用する時は以下のように読み出してあげるんだとか。app()でコンテナが得られ、その後makeコマンドを実行している。makeを追う。

```php:アプリのcontroller
$sample = app()->make('Sample');
```

⬇️
make()によって、$this->bindingsから必要なメソッド(インスタンスを作成する)を取り出し、そいつを返している。

```php:framework/src/Illuminate/Container/Container.php
class Container implements ArrayAccess, ContainerContract
{

    public function make($abstract, array $parameters = []) { 
        return $this->resolve($abstract, $parameters); 
    } 

    protected function resolve($abstract, $parameters = [], $raiseEvents = true) {

        $concrete = $this->getContextualConcrete($abstract);
        .
        .
        .
        $object = $this->build($concrete);
        .
        .
        .
        return $object;
    }

    protected function getContextualConcrete($abstract) {

        if (! is_null($binding = $this->findInContextualBindings($abstract))) {
            return $binding; //ここで$this->bindingsから必要なインスタンスを作成するメソッドを取り出している
        }
        .
        .
        .
    }

    public function build($concrete) {

        if ($concrete instanceof Closure) {
            //$concreteが無名関数ならば、発動させて返す
            return $concrete($this, $this->getLastParameterOverride());
        }
        .
        .
        .
    }
}
```

#参考文献
[Laravelのサービスプロバイダーの仕組みやメリットとは](https://www.geekfeed.co.jp/geekblog/laravel-service-providers)
[Laravel](https://github.com/laravel/framework)