Laravelの初心に還る旅【サービスコンテナ&サービスプロバイダ】

# 1. サービスコンテナの概要

## サービスコンテナとは

オブジェクトのインスタンス化を管理するもの。
（〜が要求されれば〜のインスタンスを提供するといったようなイメージ）

公式（Laravel7.x）
>Laravelサービスコンテナは、クラスの依存関係を管理し、依存関係の注入を実行するための強力なツールです。依存性注入は、本質的にこれを意味する派手なフレーズです。クラスの依存関係は、コンストラクタまたは場合によっては「セッター」メソッドを介してクラスに「注入」されます。

## 依存性注入（Dependency injection、略称DI）とは
あるオブジェクトが他のサービスオブジェクトに依存している際、そのサービスオブジェクトの生成は自身で担わず、外部から生成されたオブジェクトを受け取り、使用する仕組み。
生成されたオブジェクトに対しての、「使用」と「構築」の責務を分離する。
クライアント側はオブジェクトの生成方法は知る必要がなく、使用するのみ。
これにより、コードの可読性と再利用のしやすさが向上する。

### 例）

#### 依存性注入を行わない場合のコンストラクタ

```Foo.php
class Foo{
    private $bar;

    public function __construct()
    {
        $this->bar = new Bar();
    }
}
```

Fooクラス自身がBarクラスのインスタンス化を担っており、単一責任の原則に基づいていない。
さらに、コンストラクタ内にインスタンス生成のロジックがハードコードされているため、密結合になっている。

#### 依存性注入を行う場合のコンストラクタ

```Foo.php

class Foo{
    private $bar;

    public function __construct(Bar $bar)
    {
        $this->bar = $bar;
    }
}
```

FooクラスはBarクラスのインスタンス化は担っておらず、どこかで生成されたインスタンスをプロパティに格納するだけ。

Laravelのサービスコンテナはこの依存性注入を便利に行ってくれる。

# 2. サービスプロバイダ

## サービスプロバイダとは
Laravelアプリケーションの全体処理において、初期起動処理を担う。
サービスコンテナの結合や、イベントリスナ、フィルター、それにルートなど、諸々の登録処理を行う。

公式（Laravel7.x）
>サービスプロバイダは、Laravelアプリケーション全体の起動処理における、初めの心臓部です。皆さんのアプリケーションと同じく、Laravelのコアサービス全部もサービスプロバイダを利用し、初期起動処理を行っています。
ところで「初期起動処理」とは何を意味しているのでしょうか？　サービスコンテナの結合や、イベントリスナ、フィルター、それにルートなどを登録することを一般的に意味しています。サービスプロバイダはアプリケーション設定の中心部です。

# 3. サービスコンテナとサービスプロバイダの活用例

## 自動的な解決

デフォルトでLaravelプロジェクト内に作成されているUserモデルをもとに解説。

```app/User.php
<?php

namespace App;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email', 'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];
}

```

以下、Userモデルに合わせた形でコントローラクラスを作成。

```app/Http/Controllers/UserController.php
<?php

namespace App\Http\Controllers;

use App\User;
use Illuminate\Http\Request;

class UserController extends Controller
{
    /**
     * ユーザーモデル
     *
     * @var User
     */
    protected $user;

    public function __construct(User $user)
    {
        $this->user = $user;
    }

    public function getName(int $id): string
    {
        return $this->user->where('id', $id)->get('name');;
    }
}
```

コンストラクタに注目。

```UserController.php
     /**
     * ユーザーモデル
     *
     * @var User
     */
    protected $user;

    public function __construct(User $user)
    {
        $this->user = $user;
    }
```

コンストラクタでUserクラスをタイプヒンティングしている。

通常サービスコンテナを利用せずにUserControllerをインスタンス化しようとすると以下のようになる。

```php
$user_controller = new UserController(new User());
```

しかし、Laravelのサービスコンテナを利用する場合は以下のように書くことができる。

```php
$user_controller = app()->make(UserController);
```

なぜかというとLaravelがタイプヒンティングをもとにリフレクションで自動解決するからだ。
（引数にわざわざUserモデルのインスタンスを渡さなくても要求されているオブジェクトを自分で探してインスタンス化する。）

これでUserControllerクラスのプロパティ$usersにはUserモデルが格納される。

上記の例だと自動的に解決するが、明示的にサービスコンテナの結合を定義しておくこともできる。


## 明示的な解決

前述のUserモデルとコントローラを使用し、Repositoryパターンを用いて解説。

Repositoryパターンに必要な諸々のファイルを作成。

```app/Repositories/Interfaces/UserRepositoryInterface.php
<?php

namespace App\Repositories\Interfaces;

interface UserRepositoryInterface
{
    public function all();

    public function getUserById(int $id);
}
```

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

namespace App\Repositories;

use App\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Collection;
use App\Repositories\Interfaces\UserRepositoryInterface;

class UserRepository implements UserRepositoryInterface
{
    private $user;

    public function __construct(User $user)
    {
        $this->user = $user;
    }

    public function all(): Collection
    {
        return User::all();
    }

    public function getUserById(int $id): Model
    {
        return User::where('id', $id)->first();
    }
}

```

コントローラは以下のように書き換えておく。

```app/Http/Controllers/UserController.php
<?php

namespace App\Http\Controllers;

use App\User;
use Illuminate\Http\Request;
use Illuminate\Database\Eloquent\Model;
use App\Repositories\Interfaces\UserRepositoryInterface;

class UserController extends Controller
{
    /**
     * リポジトリクラス
     *
     * @var UserRepositoryInterface
     */
    protected $repository;

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

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

    public function getUserName(int $id): string
    {
        $model = $this->repository->getUserById($id);
        return $model->name;
    }
}
```

サービスプロバイダクラスの作成。

```terminal
root@582455531b89:/var/www/hogehoge# php artisan make:provider RepositoryServiceProvider
Provider created successfully.
```

以下のサービスプロバイダクラスが出来上がる。

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

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class RepositoryServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

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

register()内を以下のように書き換える。

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

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class RepositoryServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind(UserRepositoryInterface::class, UserRepository::class);
    }

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

これで、「UserRepositoryInterfaceクラスが要求された際にUserRepositoryクラスを返す」という意味になる。

最後にconfig/app.phpに作成したサービスプロバイダを追加しておく。

```config/app.php
...省略
    App\Providers\AppServiceProvider::class,
        App\Providers\AuthServiceProvider::class,
        // App\Providers\BroadcastServiceProvider::class,
        App\Providers\EventServiceProvider::class,
        App\Providers\RouteServiceProvider::class,
        App\Providers\RepositoryServiceProvider::class,

    ],
...省略
```

tinkerで実行してみる。

```terminal
root@582455531b89:/var/www/hogehoge# php artisan tinker
Psy Shell v0.10.3 (PHP 7.3.9-1+ubuntu16.04.1+deb.sury.org+1 — cli) by Justin Hileman
>>> $controller = app()->make('UserController');
[!] Aliasing 'UserController' to 'App\Http\Controllers\UserController' for this Tinker session.
=> App\Http\Controllers\UserController {#3009}
>>> $controller->index();
=> Illuminate\Database\Eloquent\Collection {#3054
     all: [
       App\User {#3055
         id: 1,
         name: "test1",
         email: "test1@test.com",
         email_verified_at: null,
         created_at: "2020-04-18 06:21:24",
         updated_at: "2020-04-18 06:21:24",
       },
       App\User {#3056
         id: 2,
         name: "Kaci Kunze",
         email: "acarroll@example.net",
         email_verified_at: "2020-04-18 06:43:29",
         created_at: "2020-04-18 06:43:29",
         updated_at: "2020-04-18 06:43:29",
       },
       App\User {#3057
         id: 3,
         name: "August Paucek",
         email: "oreilly.jadyn@example.org",
         email_verified_at: "2020-04-18 06:43:29",
         created_at: "2020-04-18 06:43:29",
         updated_at: "2020-04-18 06:43:29",
       },
       App\User {#3058
         id: 4,
         name: "Lizeth Reichel",
         email: "boyle.heath@example.com",
         email_verified_at: "2020-04-18 06:43:29",
         created_at: "2020-04-18 06:43:29",
         updated_at: "2020-04-18 06:43:29",
       },
       App\User {#3059
         id: 5,
         name: "Aurelio Gorczany",
         email: "polly72@example.org",
         email_verified_at: "2020-04-18 06:43:29",
         created_at: "2020-04-18 06:43:29",
         updated_at: "2020-04-18 06:43:29",
       },
       App\User {#3060
         id: 6,
         name: "Derek Boehm II",
         email: "fritz19@example.org",
         email_verified_at: "2020-04-18 06:43:29",
         created_at: "2020-04-18 06:43:29",
         updated_at: "2020-04-18 06:43:29",
       },
       App\User {#3061
         id: 7,
         name: "Nat Mertz",
         email: "amira.wisozk@example.net",
         email_verified_at: "2020-04-18 06:43:29",
         created_at: "2020-04-18 06:43:29",
         updated_at: "2020-04-18 06:43:29",
       },
       App\User {#3062
         id: 8,
         name: "Dominic Ritchie",
         email: "ybartell@example.com",
         email_verified_at: "2020-04-18 06:43:29",
         created_at: "2020-04-18 06:43:29",
         updated_at: "2020-04-18 06:43:29",
       },
       App\User {#3063
         id: 9,
         name: "Princess Cronin",
         email: "zion.weissnat@example.com",
         email_verified_at: "2020-04-18 06:43:29",
         created_at: "2020-04-18 06:43:29",
         updated_at: "2020-04-18 06:43:29",
       },
       App\User {#3064
         id: 10,
         name: "Willie Becker",
         email: "leta.medhurst@example.org",
         email_verified_at: "2020-04-18 06:43:29",
         created_at: "2020-04-18 06:43:29",
         updated_at: "2020-04-18 06:43:29",
       },
...省略

>>> $controller->getUserName(2);
=> "Kaci Kunze"
```

UserControllerをインスタンス化しただけで、UserRepositoryクラスがコントローラクラス内のプロパティDIされ、UserRepositoryのメソッドを使用できている。

# まとめ
Laravelは偉大。