前々回、前回とサービスコンテナについてやって来た続きです。今回はサービスコンテナを使った、クラスのインスタンス生成と依存性の注入を試してみます。
- クラス名でのインスタンス生成と依存性注入
- インターフェースでの依存性注入
- タイプヒントによる依存性注入
これらの機能は、これこそがLaravelの正体と言っても過言では無いくらい、あちらこちらで使われています。とても重要な機能です。
クラス名でのインスタンス生成と依存性注入
実は、サービスコンテナの make() メソッドでインスタンスを取得する際にクラス名を渡すと、事前にバインドしていなくてもクラスのインスタンスを取得することができます。
// 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 を届けました。"; } } class Messenger { // ① 追加 protected $sender; public function __construct(MailSender $sender) { // ② $this->sender = $sender; } public function send($message) { return $this->sender->send($message); } } ... /* * Routes */ Route::get('send/{message?}', function($message = '合格通知') { $messenger = $this->app->make('Messenger'); // ③ return $messenger->send($message); }); ...
① Messenger クラスを追加
② コンストラクタで MailSender クラスへの依存を注入するようにします。
③ make() メソッドにクラス名を渡すと、そのクラスのインスタンスを取得できます。
事前にサービスコンテナにクラス名でバインドしておく必要はありません。
ここで注目するのは、Messengerクラスのコンストラクタでは、②MailSenderクラスのインスタンスを必要としていますが、特に new して渡したりはしていません。Laravel のサービスコンテナは、make() を使って、クラスのインスタンスを要求されると、PHPのリフレクション機能を使って、コンストラクタの引数を判別して、MailSenderクラスのインスタンを生成し、Messengerクラスのコンストラクタに渡してくれます。つまり、コンストラクタのタイプヒントを解析して、自動的に依存性の注入を行ってくれます。
このコンストラクタのタイプヒント解析と依存性の注入は再帰的に実行されます。例えば上記の例で、もし、MailSenderのコンストラクタで他のクラスを必要としていた場合は、それも自動生成してくれます。超クールです。
インターフェースでのバインドと依存性注入
先ほどの例の Messengerクラスは依存性は外部から注入していますが、MailSenderクラスに強く結びついた状態です。この強い結びつきを排除するには、インターフェースでバインドを行う必要があります。
// app/Http/routes.php /* * Class */ ... class Messenger { protected $sender; public function __construct(SenderInterface $sender) { // ① 変更 $this->sender = $sender; } public function send($message) { return $this->sender->send($message); } } /* * Binding */ ... $this->app->bind('SenderInterface', 'MailSender'); // ② 追加 // $this->app->bind('SenderInterface', 'BikeSender'); // ②’ /* * Routes */ Route::get('send/{message?}', function($message = '合格通知') { $messenger = $this->app->make('Messenger'); // ③ return $messenger->send($message); });
① Messengerクラスのコンストラクタの依存性注入を、インターフェースに変更します。
② インターフェース名でクラスをバインドします。
②’ MailSender から BikeSender に切り替える時は、ここを修正します。
③ サービスコンテナに Messangerクラスのインスタンスを要求します。
コンテナは Messengerクラスのコンストラクタを解析し、SenderInterfaceのインスタンスを生成しようとします。そして、インターフェース名 SenderInterfaceにバインドされている MailSenderクラスを生成して、Messengerクラスのコンストラクタに渡してくれます。
タイプヒントによる依存性の注入
上記の「インターフェースでのバインドと依存性注入」の例を修正します。
// app/Http/routes.php ... /* * Routes */ Route::get('send/{message?}', function(Messenger $messenger, $message = '合格通知') { // ① // $messenger = $this->app->make('Messenger'); // ①' return $messenger->send($message); });
① make() メソッドで Messengerクラスのインスタンス生成を要求するのをやめて、クロージャーの引数にタイプヒントでMessengerを追加しました。Laravelでは、ルーティングのクロージャーでタイプヒントで記述した引数は自動的に生成してくれます。裏では、サービスコンテナが使用されていて、Messengerクラスのコンストラクタで必要としている SenderInterfaceにどのクラスのインスタンスを生成するのかを解決をしてくれます。
ここまでやると、サービスコンテナを使っているのかも分からないくらいシンプルで、もはや黒魔術のようです。
コントローラーでのタイプヒントの依存性注入
上記のタイプヒントでの依存性の注入はコントローラのコンストラクタやメソッドでも同様に動作します。
- コンストラクタインジェクション
- メソッドインジェクション
公式サイトのコントローラーのページに例があります。
http://laravel.com/docs/5.1/controllers#dependency-injection-and-controllers
まとめ
クラスのインスタンス生成と依存性の注入を理解できました。
Laravelのサービスコンテナは依存性を管理する強力なツールです。便利な半面、PHPのリフレクションを使って、依存性の注入を行っていることを知らないと、ちょっと黒魔術的で、何がどの様につながっているのかさっぱり分からないといった事になります。このような仕組みがあることを知っておけば、迷うことはありません。