Laravelアプリケーション理解⑤（サービスプロバイダ）

本記事ではLaravelのアプリケーションを理解し、より良い設計・アーキテクチャを構築できるように学習したことを簡潔にまとめています。

#目次
1.<font color="LimeGreen">Laravelのアーキテクチャ</font>
2.アプリケーションのアーキテクチャ
3.HTTPリクエストとレスポンス
4.データベース
5.認証と許可
6.イベントとキューによる処理の分離
7.コンソールアプリケーション
8.テスト
9.エラーハンドリングとログの活用
10.テスト駆動開発の実践

#１．Laravelのアーキテクチャ

##1-5.　サービスプロバイダ
サービスプロバイダとは、サービスコンテナへのバインド処理を行う機能であり、ビジネスロジックが実行される前にサービスプロバイダのメソッドが呼ばれる。
サービスプロバイダは、フレームワークやアプリケーションに含まれるサービス（機能）の初期化処理を行う目的で用意されいるである。

##サービスプロバイダの主な役割
***１．サービスコンテナへのバインド***
***２．イベントリスナーやミドルウェア。ルーティングの登録
３．外部コンポーネントを組み込む***

下記に、サービスプロバイダの定義場所であるconfig\app.phpを示す。

```php
'providers' => [
  Illuminate\Auth\AuthServiceProvider::class,
  Illuminate\Broadcasting\BroadcastServiceProvider::class,
  Illuminate\Bus\BusServiceProvider::class,
  --[略]--

  App\Providers\AppServiceProvider::class,
  App\Providers\AuthServiceProvider::class,
  App\Providers\EventServiceProvider::class,
  App\Providers\RouteServiceProvider::class,
],
```

##サービスプロバイダの基本的な動作
Laravelの処理処理で、まず各サービスプロバイダのregisterメソッドが実行される。その後、すべてのregisterメソッド処理が終わると、次にbootメソッドが呼び出される。
サービスプロバイダはIlluminate\Support\ServiceProviderクラスを継承しており、registerメソッドではほかの機能に依存していないインスタンスを取得している（必ず実装しなければならない）。
もし他の機能のインスタンスを取得して処理を実行する必要がある場合はbootメソッドを実行する（実装は任意）

下記にデータベース操作を行うDatabaseServiceProviderを示す。

```php
namespace Illuminate\Database;

use Faker\Factory as fakerFactory;
use Faker\Generator as FakerGenerator;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\ServiceProvider;
use Illuminate\Contracts\Queue\EntityResolver;
use Illuminate\Database\Connectors\ConnectionFactory;
use Illuminate\Database\Eloquent\QueueEntityResolver;
use Illuminate\Database\Eloquent\Factory as EloquentFactory;

class DatabaseServiceProvider extends ServiceProvier
{
    public function boot()
    {
      Model::setConectionResolver($this->app['db']);
      Model::setEventDispatcher($this->app['events']);
     }
  
    public function register()
    {
      Model::clearBootedModels(); // ①
      $this->registerConnectionServices(); // ②
      $this->registerEloquentFactory(); // ③
      $this->registerQueueableEntityResolver(); 
      $this->registerDoctrineTypes();
    }

    // ①
    protected function registerConnectionServices()
    {
      $this->app->singleton('db.factory', function ($app) {
        return new ConnectionFactory($app);
      });
      $this->app->singleton('db', function ($app) {
        return new DatabaseManager($app, $app['db.factory']);
      });
      $this->app->bind('db.connection', function ($app) {
        return $app['db']->connection();
      });
      $this->app->singleton('db.transactions', function ($app) {
        return new DatabaseTransactionsManager;
      });
    }

    //②
    protected function registerEloquentFactory()
    {
      $this->app->singleton(FakerGenerator::class, function ($app, $parameters) {
        $locale = $parameters['locale'] ?? $app['config']->get('app.faker_locale', 'en_US');
        if(! isset(static::$fakers[$locale])) {
          static::$fakers[$locale] = FakerFactory::create($locale);
        }
        static::$fakers[$locale]->unique(true);
        return statig::$fakers[$locale];
      });
    }

    //③
    protected function registerQueueableEntityResolver()
    {
      $this->app->singleton(EntityResolver::class, function() {
        return new QueueEntityResolver;
      });
    }
}
      
```
***＜コード解説＞***
上記のコード例のregisterメソッドでは、①registerConnectionServicesと②registerEloquentFactoryと③registerQueueableEntityResolverの３メソッドに分けて、バインド処理を定義している。

①registerConnectionServicesメソッドでは、
「db.factory」の名前で、Iluminate\Database\ConnectionFactoryのインスタンスを、
「db」の名前でIlluminate\Database\DatabaseManagerのインスタンスをどちらも<font color="Red">シングルトン</font>でバインドしている。
また、「db.connection」の名前でDatabaseManegerから取得する\Illuminate\Database\Connectionのインスタンスを取得し、バインドしている。
「db.transaction」の名前でIlluminate\Database\DatabaseTransactionsManagerのインスタンスをシングルトンバインドしている。

②registerConnectionServicesメソッドは、「FackerGenerator::class」をFakerFactory::createメソッドの戻り値をシングルトンでバインドしている。

③registerQueueableEntityResolverメソッドでは、Illuminate\Database\Eloquent\QueueEntityResolverクラスのインスタンスをシングルトンでバインドしている。

DatabaseServiceProviderクラスのスーパークラスであるServiceProviderクラスは、プロパティでサービスコンテナのインスタンスを持っているため、$this->appの形でbind()やsingleton()を呼び出さている。


##DeferrableProviderインターフェースの遅延実行
サービスプロバイダのregisterメソッドはアプリケーション起動時に実行される仕組みだが、クラスにIlluminate\Contracts\Support\DeferrableProviderインターフェースを実装すると、実行を遅らせることができる。
この場合、registerメソッドの実行タイミングを指定する必要があるため、<font color="Lime">providesメソッド</font>または<font color="Lime">whenメソッド</font>で指定する必要がある。
providesメソッドはサービスコンテナで解決する文字列を指定し、文字列の解決をサービスコンテナに依頼したタイミングで、サービスプロバイダのregisterメソッドが呼ばれ、解決が行われる。
whenメソッドはイベントを指定し、対象イベント名を配列で指定するとリスナーが党則され、その中でサービスプロバイダのregisterメソッドが実行される。
providesメソッドもwhenメソッドも、サービスコンテナでの解決やイベントの発行がアプリケーション内で実行さえっるまでインスタンス登録は行われない

下記に遅延実行の例として、キャッシュ操作を行うIlluminate\Cache\CacheServiceProviderのコードを示す。

```php
<?php
namespace Illuminate\Cache;

use Illuminate\Support\ServiceProvider;

class CacheServiceProvider extends ServiceProvider implements DeferrableProvider
{
  public function register()
  {
    $this->app->singleton('cache', fuction ($app) {
      return new CacheManager($app);
    });
    $this->app->singleton('cache.store', functin ($app) {
      return new $app['cache']->driver();
    });
    $this->app->singleton('cache.psr6', function ($app) {
      return new Psr16Adapter($app['cache.store']);
    });
    --[略]--
  }

  public function providers()
  {
    return [
        'cache', 'cache.store', 'cache.psr6', 'memcached.connector', 'cache.dynamodb.client', RateLimiter::class,
    ];
  }
} 
```
***＜コード解説＞***
上記のコードでは、providesメソッドで[cache][cache.store][cache.pst6][memcached.connector][cache.dynamodb.client]およびRateLimiterクラスを要素とする配列を返す。このため、ビジネスロジックやコントローラのDIなどで[cache]などのクラス名をサービスコンテナで解決する際に、registerメソッドが実行されロジック側にインスタンスが返される。