Laravel のドキュメントを見ていると「依存性の注入」という言葉が良く出てきます。依存性の注入は Dependency Injectionと言われ、「DI」と表記されます。また、別の表現では Inversion of Control(制御の反転)とも言われ、「IoC」と表記されます。Laravel のドキュメントでは「 loC」の方が使われています。
Laravel では、依存性の注入という概念を核としてフレームワークが作成されています。この概念を理解することが、Laravel を理解することへの近道になるかと思います。
この依存性の注入とは何でしょうか?
今回は簡単なPHPのサンプルで、この概念を理解したいと思います。
※ Laravelを使ったソースコードでは無いのでご注意ください。
依存性の注入を使っていないケース
<?php
// send_message.php
/*
* Class
*/
interface SenderInterface {
public function send($message);
}
class MailSender implements SenderInterface { // ①
public function send($message) {
// ここで、メールでメッセージを送る
return "メールで送りました。";
}
}
class BikeSender implements SenderInterface { // ②
public function send($message) {
// ここで、バイクに乗ってメッセージを届ける
return "バイク便で届けました。";
}
}
class Messenger {
protected $sender;
public function __construct() {
$this->sender = new MailSender(); // ③
}
public function send($message) {
return $this->sender->send($message);
}
}
/*
* Main
*/
// メールで送る
$messenger = new Messenger(); // ④
echo $messenger->send("合格通知") . "\n"; // "メールで送りました。"
SenderInterface を持つ、①MailSenderと②BikeSenderクラスがあります。
③Messengerクラスではコンストラクタで直接 MailSenderクラスをnewしています。
その為、④Main プログラムで、送信手段をメールからバイク便に変更しようとすると、③Messengerクラスのコンストラクタを修正する必要があります。
この状態を「Messengerクラスが、MailSenderクラスに依存している」と言います。
依存性の注入を使ったケース
<?php
// send_message_di.php
/*
* Class
*/
interface SenderInterface {
public function send($message);
}
class MailSender implements SenderInterface { // ①
public function send($message) {
// ここで、メールでメッセージを送る
return "メールで送りました。";
}
}
class BikeSender implements SenderInterface { // ②
public function send($message) {
// ここで、バイクに乗ってメッセージを届ける
return "バイク便で届けました。";
}
}
class Messenger {
protected $sender;
public function __construct(SenderInterface $sender) { // ③
$this->sender = $sender;
}
public function send($message) {
return $this->sender->send($message);
}
}
/*
* Main
*/
// メールで送る
$messenger = new Messenger(new MailSender()); // ④
echo $messenger->send("合格通知") . "\n"; // "メールで送りました。"
// バイク便で送る
$messenger = new Messenger(new BikeSender()); // ⑤
echo $messenger->send("合格通知") . "\n"; // "バイク便で届けました。"
③Messengerクラスのコンストラクタで、SenderInterfaceのクラスを取得するよう変更しました。これで特定のクラスへの依存を排除できました。
④⑤Main プログラムで送信手段を変更する事が出来るようになりました。
Messengerクラスの依存性を外部から注入できるようになり、柔軟性と保守性が上がりました。
DI コンテナを使ったケース
さらに一歩進めて、DI コンテナという仕組みを導入してみます。
この例では DI コンテナに Pimple を使用します。
https://github.com/silexphp/Pimple
まず、composerでカレントディレクトリにPimpleをインストールします。
composer require pimple/pimple:~3.0
DI コンテナを使ったサンプル
<?php
// send_message_di_container.php
require "vendor/autoload.php"; // ① Pimpleをオートロード
/*
* Class
*/
interface SenderInterface {
public function send($message);
}
class MailSender implements SenderInterface {
public function send($message) {
// ここで、メールでメッセージを送る
return "メールで送りました。";
}
}
class GMailSender implements SenderInterface { // ②
public function send($message) {
// ここで、GMailでメッセージを送る
return "GMailで送りました。";
}
}
class BikeSender implements SenderInterface {
public function send($message) {
// ここで、バイクに乗ってメッセージを届ける
return "バイク便で届けました。";
}
}
class Messenger {
protected $sender;
public function __construct(SenderInterface $sender) {
$this->sender = $sender;
}
public function send($message) {
return $this->sender->send($message);
}
}
/*
* Config
*/
$container = new Pimple\Container(); // ③ DIコンテナ
$container['mail'] = function($container) { // ④
// return new MailSender();
return new GMailSender();
};
$container['bike'] = function($container) { // ⑤
return new BikeSender();
};
/*
* Main
*/
// メールで送る
$messenger = new Messenger($container['mail']); // ⑥
echo $messenger->send("合格通知") . "\n";
// バイク便で送る
$messenger = new Messenger($container['bike']); // ⑦
echo $messenger->send("合格通知") . "\n";
①composerでインストールしたPimpleをオートロードします。
②GMailSenderを追加しました。
③DIコンテナとしてPimpleをnewします
④⑤ ‘mail’, ‘bike’ というキーワードに対してどのクラスを生成するかの設定をします。
⑥⑦DIコンテナにキーワードを渡すことでSenderInterfaceを持つクラスを生成します。
Configとして、DIコンテナにクラスの生成を委譲することで、Mainプログラム部分も依存性を排除することができました。メールで送信する時の実際の手段を②GMailSenderに変更する時は、④DIコンテナを修正します。
DIコンテナを導入したことで、MessengerクラスとSenderInterfaceを持つクラスの対応付けを1箇所に集約することができ、柔軟性、保守性ともに、アップしました。
上記のサンプルでは Mainプログラムの部分のコードが少ないので、DIコンテナの恩恵があまり感じられないかもしれませんが、コートがもっと増えて、クラスをnewする箇所があっちこっちに出てくるようであれば、このDIコンテナの導入が非常に有効になってきます。
まとめ
依存性の注入のイメージはなんとなく掴めたでしょうか?
今回のサンプルではDI コンテナとして Pimpleを使いましたが、Laravel では Illiminate\Foundation\Application クラスが DI コンテナにあたります。DI コンテナを中心としてサービスプロバイダやファサードが作られています。
また、今回のサンプルにはありませんでしたが、DIを導入すると、テストコードを書く際に、クラスをスタブやモックに入替えることが出来るようになります。もしかするとこちらの方がメリットが大きいのかもしれません。