Laravel 5.1 のサービスコンテナはクラス間の依存性を管理する為の仕組みです。以前に「依存性の注入」に書いた、DIコンテナにあたるのが Laravel のサービスコンテナです。今回は簡単なサンプルを使って、サービスコンテナの基本的な仕組みを理解したいと思います。
クラス定義(準備)
サンプルコードで使用するクラスを定義します。
// app/Http/routes.php /* * Class */ interface SenderInterface { public function send($message); } class MailSender implements SenderInterface { public function send($message) { // ここで、メールでメッセージを送る return "メールで $message を送りました。"; } } class BikeSender implements SenderInterface { public function send($message) { // ここで、バイクに乗ってメッセージを届ける return "バイク便で $message を届けました。"; } }
SenderInterface を持つ Mailsender と BikeSender クラスを定義します。
今回のサンプルでは、手軽にサービスコンテナの動きを確かめる為に、全て app/Http/routes.php 内に記述します。
※実際のプロジェクトでは、クラスの定義は別ファイルに切り出し、サービスコンテナのバインド処理はサービスプロバイダに記述するようにしてください。
サービスコンテナを使わないケース
// app/Http/routes.php /* * Class */ ... /* * Routes */ Route::get('send/{message?}', function($message = '合格通知') { $sender = new MailSender() // ① // $sender = new BikeSender() // ② return $sender->send($message); });
ルーティングのクロージャーで具象クラスである①MailSenderを直接 new しています。クロージャーが MailSenderクラスに依存している状態です。
実際の開発で、様々な箇所で ①MailSenderが new されている場合、MailSender を ②BikeSender に変更したくなった時は、あっちこっち修正しなければなりません。修正漏れの発生やバグが混入する可能性も高くなります。
この例ではMailSender, BikeSenderともにシンプルですが、MailSenderがAクラスに依存していて、AクラスがBクラスに依存にしていて、といった感じで、構造をもった場合、多段階にnewを行って、依存性の注入を行う必要があり、修正が大変です。
また、具象クラスに依存している為、テストを書く時に、モックやスタブに差し替えることができず、テストが非常に困難です。
サービスコンテナを使ったケース
app/Http/routes.php を以下の様に修正します。
// app/Http/routes.php /* * Class */ ... /* * Binding */ $this->app->bind('sender', 'MailSender'); // ① /* $this->app->bind('sender', function($app) { // ①’ return new MailSender(); }); */ // $this->app->bind('sender', 'BikeSender'); // ② /* * Routes */ Route::get('send/{message?}', function($message = '合格通知') { $sender = $this->app->make('sender'); // ③ // $sender = $this->app['sender']; // ④ // $sender = App::make('sender'); // ⑤ return $sender->send($message); });
① routes.php内では $this->app は Illiminate\Foundation\Application クラスのインスタンスになります。このクラスがサービスコンテナになります。
ここでは、サービスコンテナにbind()メソッドでキー名にクラスを紐付けて登録しています。①と①’のクロージャーを使った記述は実質的に同じです。
② MailSenderの代わりにBikeSenderを使いたい時は①をコメントにして、こちらを使います。
③ サービスコンテナからキー名に対応するクラスを生成します。ここではMailSenderのインスタンスが返ってきます。
④ サービスコンテナはPHPの ArrayAccess を実装しているので、配列としてアクセスすることも可能です。③と④は実質的に同じです。
⑤ Appファサードを使ってサービスコンテナにアクセスすることもできます。③④⑤は実質的に同じです。
サービスコンテナを使ったことで、③④⑤の箇所でのクラスへの依存を排除し、クラスに依存している箇所をサービスコンテナの設定にあたる、①②Binding箇所に集約することができました。
クラスの生成がBinding箇所に集約されたことで、構造を持った複雑なクラスを生成する場合でも、クラス構造はBinding箇所のみに隠蔽されました。
テストコードを書く時にも、サービスコンテナを使って、クラスをモックやスタブに差し替えることができるようになりました。
まとめ
サービスコンテナについて基本的な仕組みと使い方を理解することができました。
- サービスコンテナへのクラスのバインド方法(キー名とクラスの紐付け)
- サービスコンテナからのインスタンス取得方法(キー名でのクラスインスタンスの生成)
- Appファサードを使ったサービスコンテナへのアクセス
DIコンテナのメリット
- クラスの生成を1箇所に集約できる
- クラスの構造を隠蔽できる
- モックを使ってテストがしやすくなる
→ 保守性、柔軟性、テスト性が向上する
Laravel のソースコードを見ていると、頻繁にサービスコンテナを使用している箇所に遭遇します。サービスコンテナの基本を理解しているとソースコードを調べやすくなります。
公式サイト
http://laravel.com/docs/5.1/container
積み残した課題
今回の内容では以下の事を積み残しました。以下の関連記事もご覧ください。
-
サービスコンテナのバインド
bind() メソッド以外のバインド方法。singleton()や instance() 。 -
サービスコンテナの依存性注入
クラス名やインターフェース名での依存性注入やタイプヒントでの依存性注入 -
サービスプロバイダーの作成
サービスプロバイダー内でのサービスコンテナのバインド