ディペンデンシーインジェクション
Aura DIパッケージが提供するDIコンテナシステムには下記の特徴があります。
ファクトリークラスが合成される時にオブジェクトの構成、オブジェクトの生成、オブジェクトの利用は完全に分離されます。
高い柔軟性とテスト可能性を可能にします。
DIの性質と利点を最大限理解するために、”inversion of control” や “dependency injection”をhttp://martinfowler.com/articles/injection.html by Martin Fowler で調べて下さい。
コンテナの生成
Aura DIパッケージは新規のDIインスタンスを返すスクリプトが含まれています:
あるいはAura DIの'src/'
ディレクトリをあなたのオートローダーに追加して、自身でインスタンス生成します:
Container
はDIコンテナです。サポートするオブジェクトは:
これらのサポートオブジェクトを直接利用することはありません。 Container
のメソッドがそれらのオブジェクトにアクセスします。
サービスの設定
以下の例のではデータベース接続を返すサービスをセットする必要があります。例えばデータベースの接続クラスは以下のようになります:
ごく単純なやり方から洗練された方法に移行するために4つのステップを踏みます。
どのDIコンテナの利用でも利点と弱点があります。
方法 1: 早期読み込み
早期読み込み (eager loading)では new
演算子でインスタンスをつくりサービスを生成します。
この方法ではサービスを セットするタイミングで データベースオブジェクトが作られます。
つまりコンテナから取り出される事がなくても生成されるということです。
方法 2: 遅延読み込み
遅延読み込み (lazy loading)では new
で生成するサービスをクロージャでラップして生成します。
この方法ではデータベースオブジェクトはコンテナから$di->get('database')
で 取得 する時に生成されます。
オブジェクト生成をクロージャをラッピングすることでデータベースオブジェクトの読み込みを遅延読み込みする事ができます。
もし$di->get('database')
を行う事がなければオブジェクトが生成される事はありません。
方法 3: コンストラクタ引数
この方法ではnew
演算子を取り除きます。その代わりに$di->newInstance()
メソッドを使用します。
遅延読み込みと同じように生成をクロージャでラップします。
newInstance()
メソッドはForge
オブジェクトを使ってコンストラクタメソッドを反映させオブジェクトを生成するために使われます。
コンストラクタには 名前-値 とペアになった連想配列を渡します。順序は関係ありません。
存在しないパラメータはクラスコンストラクタで定義されているデフォルトが使われます。
方法4: クラスコンストラクタ引数
この方法ではDatabase
クラスの設定をDatabase
オブジェクトの遅延生成から分離して定義します。
オブジェクトの生成プロセスの中で Forge
クラスは$di->params
の値をクラスを生成するために調べます。
その値はクラスコンストラクタのデフォルト引数の値とマージされ、コンストラクタに渡します。
(順序は関係ありません。引数の名前が一致するかを調べます)
オブジェクトの設定と生成をうまく分け、またコンテナからサービスオブジェクトが遅延読み込みできています。
方法 5: lazyNew() メソッドのコール
この方法ではlazyNew()
メソッドをコールして「クロージャを使って新しいインスタンスを返す」と同様の事を行っています。
方法 5a: コンストラクタ引数のオーバーライド##
この方法では インスタンス化する時に使う$di->params
をオーバーライドします。
インスタンス時に指定する値はコンフィギュレーションでの値(コンストラクタのデフォルトより優先される)より優先されます。
サービス取得
コンテナからサービスを取得するために $di->get()
と呼びます。
これでコンテナからサービスオブジェクトを取り出す事ができます。
もしそれがクロージャなら実行されオブジェクトが生成wqれます。
一旦オブジェクトが生成されるとその後何回取り出そうとしても同じインスタンスが返ります。
コンストラクタ引数の継承
この例にサンプルに従いAbstractModel
を追加して、二つのコンクリートクラスBlogModel
とWikiModel
を追加します。
全てのAbstractModel
クラスは 1つまたはそれ以上のテーブルのが必要なDatabase
接続を必要としています。
BlogModel
と WikiModel
を作成します。そしてサービス定義のとおりにそれらにデータベースサービスをインジェクトします。
DIコンテナによって継承された設定を使って、クラスコンフィギュレーションによるデータベースサービスの定義ができます。
BlogModel
モデルやWikiModel
のために直接'db'
のパラメーターの値をセットしたりすることはありません。
その代わりにBlogModel
と WikiModel
クラスはAbstractModel
クラスを継承するので、'db'
をコンストラクタ引き数に持つ
全てのModel
クラスは自動で'database'
サービスを受け取る事ができます(インスタンス時に行う事もできます)
lazyGet()
メソッドの利用に注意してください。この特別なメソッドはパラメーターとセッターのために使われます。
もし$di->get()
を行うとコンテナはその時にサービスをインスタンス化します。
しかしながら$di->lazyGet()
の利用ではオブジェクトが設定されている場合にのみサービスがインスタンス化されます。
(レイジーロードされる)サービスのレイジーローディングラッパーとして考えてみて下さい。
これらのコンフィギュレーションのため、特別な方法で私たちのクラスを記述する必要はありません。
どのクラスでもコンストラクタのパラメターはコンフィギュレーションによって取り扱われます。
だから$di->newInstance()
や $di->lazyNew()
でインスタンス化する事ができるのです。
ファクトリーと依存解決
我々のアプリケーションのそれぞれのモデルオブジェクトのサービスをつくるのはなかなか大変な事です。
モデルを作る必要はあるかもしれませんが、それぞれが必要とするサービスは別々につくりたくないものです。
加えて説明すると、他のオブジェクトからモデルのオブジェクトをつくる必要はあります。
モデルのオブジェクトは本当に必要となるまでつくりたくありません。このためにファクトリーを使う事ができます。
下記のように3つの新しいクラスを定義します:
モデルオブジェクトをつくるファクトリークラス。
モデルファクトリーを使うアブストラクトのPageController
クラス。
それにブログモデルのインスタンスを必要とするBlogController
クラス。
モデル名にオブジェクトをつくるファクトリーをマップしたModelFactory
が、マップされたオブジェクトを生成します。
これでDIコンテナが以下のようにセットアップされています。
BlogController
のインスタンスを作成して、実行します…
依存を満たす為に2つのステップでイベントが起こります。
最初のステップはBlogController
のインスタンス化です。
-
BlogController
インスタンスはPageController
からパラメーターを継承しています。
-
PageController
のパラメーターは'model_factory'
サービスを取得します。
-
ModelFactory
パラメーターはDatabase
オブジェクトを取得します。この時データベース接続が作られます。
The second step is the invocation of ModelFactory::newInstance()
within
BlogController::exec()
:
次のステップでは ModelFactory::newInstance()
がBlogController::exec()
の中で実行されます。
これら全てが終わると BlogController::exec()
メソッドは全てが設定されたBlogModel
オブジェクトをローカルでは何の設定をすることなしに取得することができました。
Setter Injection
これまで、コンストラクタインジェクションの動作を見て来ました。セッターインジェクションも同様に機能します。
以下のサンプルクラスが与えられます…
… これでセッターメソッド経由でのインジェクションのための値を設定することができます。
lazyGet()
をインジェクションのために使ってる事に注意してください。
これはコンストラクタのパラメーターにContainer
で共有しているオブジェクトの代わりに、新しいDatabase
オブジェクトを使うように指示しています。
セッターの設定は継承されます。もしExample\Package\Foo
クラスのように継承したクラスなら…
新しいセッターのための値を加える必要はありません。 Forge
は全ての親クラスのセッターを読み込みそれらに適用します。
(もしセッターの値を追加したなら、親クラスのセッターもオーバーライドされます)
まとめ
パラメーター、セッター、サービス、ファクトリーで適切に依存を作成することができれば、DIコンテナからは直接オブジェクトを取得するのは1つのオブジェクトだけです。
全てのオブジェクトはファクトリーオブジェクトやForge
オブジェクトを通じてDIコンテナから生成されます。オブジェクト作成のためにDIコンテナが必要となることは決してありません。