はじめに
Ray.Di を使ったアプリケーションには2つのタイムステージがあります。
- コンパイルタイム:PHPファクトリーコードの自動生成
- ランタイム:アプリケーションの実行
コンパイルの対象になるのは、DIインジェクタに 束縛(bind)
の定義を知らせたサービスだけです。ソースコードを書いている時点で分かっている情報だけを使ってオブジェクトを生成できる場合は問題ありませんが、生成時に事前には分かっていない変数値を使いたい場合はどうするのが良いでしょうか?この問題に対する解決として、ファクトリとアシスティッドインジェクションを紹介し、それぞれ特徴を記します。
手組みによるファクトリ
ファクトリ(Factories)
は、値オブジェクト、モデル/ドメインオブジェクト(エンティティ)や、パラメータ設定と依存を結び付けるオブジェクトの生成を行うためのパターンとして確立されています[※1]。たとえば、クラスが複数のパラメータを取り、DIインジェクタから得られるパラメータとそうでない(ランタイムの)呼び出し元から取るパラメータと両方が必要である場合について考えてみます。
class RealPayment implements Payment
{
public function __construct(
CreditService $creditService, // DIインジェクターから
AuthService $authService, // DIインジェクターから
Date $startDate, // インスタンスの生成元から
Money $amount // インスタンスの生成元から
)
{
//...
}
}
この問題に対して定番となっている解決方法は、DIで生成できるファクトリクラスを書くことです。
class RealPaymentFactory implements PaymentFactory
{
private $creditService;
private $authService;
/**
* @Inject
*/
public function __construct(CreditService $creditService, AuthService $authService) {
$this->creditService = $creditService;
$this->authService = $authService;
}
public function newInstance(Date $startDate, Money $amount)
{
return new RealPayment($this->creditService, $this->authService, $startDate, $amount);
}
}
そして、モジュールで適切に束縛します。
$this->bind(PaymentFactory::class)->to(RealPaymentFactory::class);
特徴
- 生成と実行の分離を保てる
- ボイラープレートの(定型的な)ファクトリクラスを書く必要がある
アシスティッドインジェクション
Ray.Di にはメソッド実行時に依存を渡すことができる アシスティッドインジェクション
[※2] があるので、これを利用するやり方も見ていきます。アシスティッドインジェクションを行うには、インジェクタに知られている引数に対して @Assisted アノテーションを付けて、引数リストの終わり(右)にこれを移動します。
class RealPayment implements Payment {
/**
* @Assisted({"creditServie"})
*/
public function newInstance(
Date $startDate, // インスタンスの生成元から
Money $amount, // インスタンスの生成元から
CreditServiceInterface $creditService = null // アシストされる
)
{
//...
}
}
メリット
- ボイラープレートのファクトリコードをなくすことができる
制約
- これは引数リストの終わりに対するアシストなので、複数パラメータを依存定義にしたい用途では利用できない。また、インターフェイスベースにしておく必要がある(アンターゲットバインドのアシストはできない)。
BEARアプリケーションで変数値を使ったオブジェクト生成を行う場合について
実務レベルだとリソースやインターセプタが複雑な仕事を担うことがあります。このような場合は、サービスクラスをインジェクトして利用するパターンが有用です。サービスクラスの定義ではコンストラクタ引数に変数値を取りたいことがあります。コンパイルタイムで確定できる情報なのであれば、インスタンス束縛
、コンストラクタ束縛
、コンテンキストプロバイダ束縛
を利用できますが、実行時にしか変数値が決まらないケースでは上記の検討となります。ファクトリに new
で生成させたサービスでは(コンパイル対象外なので)AOPが利用できなくなる点には留意が必要です。
補足:単純な値オブジェクトの場合
単純で final
が付けられるような値オブジェクトであればコンパイル対象外としてユーザサイドで new
して使う方が適切であることが多いでしょう。