- Interfaceを利用する意義
- Laravelでどうやって注入するの?
- サービスプロバイダとは
- register メソッドでDIの設定を書く
- Interfaceへの実装注入以外の用途でも使える
- まとめ
Interfaceを利用する意義
LaravelはDIを勝手にいい感じにしてくれます。例えばこうです。
<?php class RiotGamesApi { public function getAllChampions(): array { // 実装 } } class Controller { public function __construct(RiotGamesApi $riptGamesApi) { $this->riotGamesApi; } public function get() { return $this->riotGamesApi->getAllChampions(); } }
この場合はLaravelがいい感じに Controller
のコンストラクタに RiotGamesApi
を注入してくれるということはみんな知っているでしょう。*1
さて、RiotGamesApi には色々なメソッドが生えている可能性があります。例えばこうです。
<?php class RiotGamesApi { public function getAllChampions(): array { // 実装 } public function getSummonerData(string $summonerName): array { // 実装 } } class Controller { public function __construct(RiotGamesApi $riptGamesApi) { $this->riotGamesApi; } public function get() { return $this->riotGamesApi->getAllChampions(); } }
ここで、Controller
は RiotGamesApi
クラスのうち getAllChampions
メソッドしか利用していません。そこで interface
を利用して以下のように表現できます。
<?php class RiotGamesApi implements ChampionGetter { public function getAllChampions(): array { // 実装 } public function getSummonerData(string $summonerName): array { // 実装 } } interface ChampionGetter { public function getAllChampions(): array; } class Controller { public function __construct(ChampionGetter $championGetter) { $this->$championGetter; } public function get() { return $this->$championGetter->getAllChampions(); } }
Controller
のコンストラクタは ChampionGetter
インタフェースで受け取るわけです。 RiotGamesApi
クラスは ChampionGetter
を実装しているので Controller
に注入することができるわけです。
こうするとどこがありがたいのか?もう少し例を拡張してみます。
<?php class RiotGamesApi implements ChampionGetter, SummonerGetter { // 略 } class ChampionCache implements ChampionGetter { // 略 } interface ChampionGetter { public function getAllChampions(): array; } interface SummonerGetter { public function getDummonerData(string $summonerName): array; } class Controller { public function __construct(ChampionGetter $championGetter) { $this->$championGetter; } public function get() { return $this->$championGetter->getAllChampions(); } }
インタフェースとして ChampionGetter
と SummonerGetter
が登場してきました。RiotGamesApi
クラスは両方実装しています。ChampionCache
は ChampionGetter
だけしか実装していません。
Controller
は ChampionGetter
を要求しているので、RiotGamesApi
でも ChampionCache
でもどっちでもいいわけです。ここはインフラ側の都合で、APIに利用制限がないなら RiotGamesApi
を、APIに利用制限があったり、実行速度の問題があるなら ChampionCache
を使うなどの使い分けができるわけです。
一方で SummonerGetter
を要求している場合は、こちらはキャッシュはありませんので、 RiotGamesApi
を使うしかないということがわかります。
このようなことがコード上で表現できるのがインタフェースの一つの利点です。
Laravelでどうやって注入するの?
さてここで疑問です。
<?php class Controller { public function __construct(RiotGamesApi $riptGamesApi) { $this->riotGamesApi; } }
この場合Laravelが勝手にDIしてくれました。
<?php class Controller { public function __construct(ChampionGetterInterface $championGetter) { $this->$championGetter; } }
この場合 ChampionGetterInterface
はインタフェースなので勝手にDIはしてくれません。インタフェースを実装しているクラスは複数ある可能性があり、どれを注入していいか明確でないからです。
こういった場合はサービスプロバイダをにDI設定を記述することになります。
サービスプロバイダとは
サービスプロバイダは、app/Providers
以下にあるクラス群です。デフォルトで AppServiceProvider
というプロバイダがあると思います。
<?php namespace App\Providers; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { /** * Register any application services. * * @return void */ public function register() { // } /** * Bootstrap any application services. * * @return void */ public function boot() { // } }
サービスプロバイダは、アプリケーションが起動した時(ユーザーからのHTTPリクエストを受けた時)に最初に実行される処理を記述したものです。Bootstrap的なものです。
サービスプロバイダには register
と boot
二つのメソッドがあります。Laravelに登録された全てのサービスプロバイダの register
が呼ばれた後、Laravel に登録された全てのサービスプロバイダの boot
が呼ばれます(呼ばれる順序が重要です)。
Laravelに登録されているサービスプロバイダ一覧は config/app.php
に書かれています。
<?php ... 'providers' => [ /* * Laravel Framework Service Providers... */ Illuminate\Auth\AuthServiceProvider::class, Illuminate\Broadcasting\BroadcastServiceProvider::class, // 略 /* * Application Service Providers... */ App\Providers\AppServiceProvider::class,
AppServiceProvider
もここに登録されているのがわかります。
Laravel公式のサービスプロバイダのドキュメントは以下です。
register メソッドでDIの設定を書く
Laravelではこの register
メソッドで DI の設定を書いていくことになります。Laravelのサービスプロバイダのドキュメントには
registerメソッドの中ではサービスコンテナへの登録だけを行わなくてはなりません。
と書いてあります。サービスコンテナのドキュメントは以下なのですが、
Laravelのサービスコンテナは、クラス間の依存を管理する強力な管理ツールです。依存注入というおかしな言葉は主に「コンストラクターか、ある場合にはセッターメソッドを利用し、あるクラスをそれらに依存しているクラスへ外部から注入する」という意味で使われます。
要するにサービスコンテナというのはDIの設定を管理してくれるツールということです。
新しくサービスプロバイダを作ってもいいですが、とりあえず AppServiceProvider
にDI設定を書いていくことにします。書き方はこんな感じです。
<?php class AppServiceProvider extends ServiceProvider { /** * Register any application services. * * @return void */ public function register() { $this->app->singleton(ChampionGetter::class, function($app) { return $app->make(RiotGamesApi::class); }); } }
これは「 ChampionGetter
というインタフェースがが指定されたらその実装は RiotGamesApi
を使いますよ。 RiotGamesApi
はシングルトンとして使います」といった意味の記述になります。
Interfaceへの実装注入以外の用途でも使える
例えばこういう使い方もできます。
<?php class RiotGamesApi { public function __construct(string $apiKey) { $this->apiKey = $apiKey; } } class AppServiceProvider extends ServiceProvider { public function register() { $this->app->singleton(RiotGamesApi::class, function ($app) { return new RiotGamesApi(env('API_KEY')); }); } }
このように、string型は普通は勝手に注入することができないのですが、サービスコンテナに登録することでenvから取ってきて注入するということができます。
まとめ
- Laravelでインターフェースに実装を注入するにはサービスプロバイダを使う
- サービスプロバイダの
register
にサービスコンテナへの登録(=DI設定)を書く - インタフェースをうまく使っていこう!
Clean Architecture 達人に学ぶソフトウェアの構造と設計
- 作者:Robert C.Martin
- 発売日: 2018/07/27
- メディア: 単行本
- 作者:Andrew Hunt,David Thomas
- 発売日: 2016/10/20
- メディア: 単行本(ソフトカバー)
*1:なぜこのようにコンストラクタで注入するかの説明は今回はしません。