フレームワークのキモ、サービスコンテナの仕組みを学ぶ
フレームワークは様々な機能を持つ数多くのクラスで構成されている。
これらのクラスのインスタンスを管理するのがサービスコンテナです。
サービスコンテナとは
複数のクラスが同じ機能を利用する際、利用するクラスに応じてインスタンスが生成される。これはコードの複雑化に繋がり効率、処理漏れの部分でも不具合を招く可能性が高い。これに対し、インスタンス管理の役割を担っているのがサービスコンテナです。
サービスコンテナに対してインスタンスを「要求」するだけです。
また、クラス内で必要となる機能クラスのインスタンスを、コンストラクタやメソッドの引数などを使って外部から渡すDI/依存性の注入パターンを利用する場合も、注入するインスタンスの生成やクラスへの注入をサービスコンテナが担います。
バインドと解決
サービスコンテナにインスタンスの生成方法を登録する処理は「バインド(bind)」と呼ばれ、指定されたインスタンスをサービスコンテナが生成して返すことを「解決(resolve)」と呼ぶ。
サービスコンテナどこにあんの
Illuminate\Container\Containerクラスにサービスコンテナの機能の多くが実装されている。
しかし、実態はIlluminate\Foundation\Applicationクラスで定義されており、継承して使用している。
バインドについて
バインドにはいくつかの方法があります。
1.bindメソッド
2.bindfメソッド
3.singletonメソッド
4.whenメソッド
bindメソッド
もっとも利用される。第一引数に文字列、第二引数にインスタンスの生成処理をクロージャで指定します。
例)
app()->bind(Number::class,function(){
return new Number();
});
解決について
解決したインスタンスを取得する方法は以下の通りです。
1.makeメソッド
2.appヘルパ関数
makeメソッド
makeメソッドは引数に対象の文字列を指定します。メソッドを実行すると、指定された文字列にバインドされた処理を実行して、その戻り値を返します。
app()->bind(Number::class, functiuon(){
return new Number();
});
$number1 = app()->make(Number:class);
DI(依存性の注入)って何?
例文で説明していきます。
下記はクラスが別クラスと依存関係にあるコードです。
<?php
class UserServices{
public function notice($to, $message){
$mailsender = new Mailsender();
$meilsender -> send($to, $message);
}
}
class MailSender{
public function send($to, $message){
// send mail・・・
}
}
?>
noticeメソッドでMailSenderクラスのインスタンスを生成して利用します。
noticeメソッド、UserServicesの動作にはMailSenderクラスが必要です。
これが、UserServiceクラスはMailSenderクラスに依存している状態です。
何がダメなの?
アプリケーションの変更や拡張柔軟に対応するためには、拡張可能なプログラミング設計が不可欠です。
昨日やクラス同士が密接に依存する設計になっている場合、たった1つのクラスの差し替えがプログラミングに広範囲な影響を及ぼし、多くの修正やテストが必要になります。
ダメではないが、面倒極まりないし間違え易いということです。
どうすればいい?その1
noticeメソッドの引数としてMailSenderクラスのインスタンスを渡すように修正します。
コードで確認しましょう。
class UserServices{
public function notice(MailSender $mailsender, $to, $message){
$meilsender -> send($to, $message);
}
}
引数でインスタンスを与えました。
noticeメソッドではMailSenderクラスだけではなく、その継承クラスも使用できるようになり、特定のクラスとの依存関係を排除できます。このようにクラスやメソッド内で利用する機能を外部から渡す設計パターンがDI(依存性の注入)です。
どうすればいい?その2
抽象クラス(インターフェース)に依存させることで、クラス間の関係はさらに疎結合となり、DIの恩恵をより受けることが可能になる。
class UserService{
public function notice(NotifierInterface $notidier, $to, $message){
$notifier -> send($to, $message);
}
}
interface NotifierInterface{
public function send($to, $message);
}
class MailSender implements NotifierInterface{
public function send($to, $message){
//send mail
}
}
class PushSender implements NotifierInterface{
public function send($to, $message){
//send push notdication
}
}
「通知する」役割を担うNotifierInterfaceインターフェースを定義し、UserServiceクラスのnoticeメソッドの引数は、Notifierインターフェースへ依存するように変更しています。メール送信を行うMailSenderクラスとプッシュ通知を行うPushSenderクラスに対して、このインターフェースを実装しています。
この変更で、noticeメソッドに対してmNotifierInterfaceを実装したクラスであればなんでも引数として渡すことができます。
具象クラスではなく抽象クラス(インターフェース)に依存させることで、クラス間の関係はさらに疎結合となりDIの恩恵をより受けることが可能になります。
コンストラクタインジェクション
クラスのコンストラクタの引数でインスタンスを注入する方法を、コンストラクタインジェクションと呼びます。