Aura.Di

Ray.DiはAura.Diを使用しています。AuraはPHP5.3用フレームワークで、Paul M.Jones.氏がリードのPHP5.2用フレームワークSolarPHPの現在のメジャーバージョンです。有名なフレームワークでは無いかもしれませんが、ライブラリファースト、コンパクトでクリーンなコード、100%テストカバレッジ等、リファレンスとすべき多くの点があるのではと思います。

Ray.Diは基本的にはアノテーションベースのDIコンテナですが、アノテーションを全く使わないAura.Diの上に構築されています。なのでどちらの方法でも依存性の注入を行う事ができます。前回の記事ではアノテーションを使った方法だけ紹介しましたが、この記事では両方の方法を紹介してそれぞれ比較したいと思います。

まずはそのどちらも使えるインジェクターの生成からです。

インジェクターの生成

Containerクラスのインスタンスと、インターフェイスとクラスを紐付けるモジュールの二つを引き数に取ります。ContainerクラスにはForge、ForgeにはConfig、ConfigにはAnnotationインスタンスが必要です。

$di = new Injector(new Container(new Forge(new Config(new Annotation))), new AppModule);

あるいは、

instance.php

require_once  '/path/to/Ray.Di/src.php';
return new Injector(new Container(new Forge(new Config(new Annotation))), new AppModule);

このようにincludeを使って

$di = include '/path/to/scripts/instance.php';

インスタンスをスクリプトから代入します。

クリーンな依存関係

使用される全てのクラスがインターフェイスを持ち、それぞれ必要とされるクラスのコンストラクタで受け取っています。固定化されたクラス関係は存在せずクラスの依存関係はクリーンで、ユーザー作成のコンポーネントとも入れ替え可能です。DIコンテナが扱うクラスだけでなく、DIコンテナそのものも実装(実クラス)ではなく、インターフェイスでつながれています。1

コンストラクタ・インジェクトション

Ray.Diはコンストラクターインジェクションとセッターインジェクション(メソッドを使ったインジェクション)をサポートします。2。3rdパーティのものや既存のライブラリ等、アノテーションが使えない場合の方法と使う方法を別にして紹介します。

ターゲットクラス

ターゲットになるクラスです。ListerクラスのコンストラクタにFindインターフェイスを実装したインスタンス(Finder)を渡す必要があります。

namespace MovieApp {
    class Lister {
        public $finder;
        public function __construct(Find $finder){
            $this->finder = $finder;
        }
    }
    class Finder implements Find {}
    interface Find{}
}

アノテーションを使わないコンストラクタ・インジェクション

イーガーセット

    $di = include __DIR__ . '/scripts/instance.php';
    $di->getContainer()->params['MovieApp\Lister'] = array(
       'finder' => new MovieApp\Finder
    );
    $lister = $di->getInstance('MovieApp\Lister');

params[クラス名]として、ネームドパラメーター3 で引き数を指定します。この準備は通常アプリケーションのブート時等に1度だけ行います。getInstance()時にはコンストラクタ引き数を指定していませんが、”予約”した方法で引き数が渡されインスタンスが生成されます。

レイジーセット

    $di = include __DIR__ . '/scripts/instance.php';
    $di->getContainer()->params['MovieApp\Lister'] = array(
        'finder' => $di->getContainer()->lazyNew('MovieApp\Finder')
    );
    $lister = $di->getInstance('MovieApp\Lister');

イーガーセットでは準備の段階で引き数に必要なインスタンスを生成しましたが、もしかしたら使わないかも、あるいは準備時にはまだインスタンスが確定できないものはlazyNewというメソッドを使ったレイジーセットが行えます。インスタンスの代わりにインスタンスの生成方法をセットしておいてgetInstance()時に遅延実行されコンストラクタ引き数として渡されます。引き数1つめにクラス名、2つ目に引き数をネームドパラメーターで指定します。

クラス同様、コンストラクタインジェクションの設定も親クラスから小クラスに継承されます。つまり、Finderクラスを継承した子クラスの取得時にも適用されます。またgetInstance()の第二引き数でインスタンス取得時に、設定した引き数を指定したパラメーターだけ上書きすることができます。4

アノテーションを使うコンストラクタ・インジェクション

ターゲットのメソッドに@Injectアノテーションでマークします。Ray.Diにインスタンスを代入しなければならない事が伝わります。

namespace MovieApp {
    class Lister {
        public $finder;
        /**
         * @Inject
         */
        public function __construct(Find $finder){
            $this->finder = $finder;
        }
    }
    class Finder implements Find {}
    interface Find{}
}

AbstractModuleを継承したモジュールのconfigureメソッド内でインターフェイスと実クラスを指定します。AbstractModuleにはインターフェイスとクラスを結ぶ様々なメソッドがあり、英語表現のようなDSL5 でインターフェイスとクラスを紐づけます。

    class Module extends \Ray\Di\AbstractModule
    {
        public function configure()
        {
            $this->bind('MovieApp\Find)->to('MovieApp\Finder')->in(Scope::SINGLETON);
        }
    }

前回の記事ではインスタンスを直接してしましたが、この例では実クラスを指定してin()でそのクラスはシングルトンスコープで利用されるように指定しています。二回目以降の注入には同じインスタンスが再利用されます。

    $di->setModule(new Module);
    $lister = $di->getInstance('MovieApp\Lister');

そのモジュールをセットしたインジェクターでインスタンスを取得します。

Conclusion

アノテーションを使用しないでクラス名やメソッド名を指定してそこの何を入れるかを指定する方法と、アノテーションを使ってインジェクトするポイントを指定しインターフェイスとクラスをワイアリングする方法と、依存オブジェクトの2つの指定の方法、Ray.Diはそのどちらも可能という事を見てきました。前者はXMLやYAMLファイルなどのスタティックな設定を持つことが多く、Symfomy2やFlow3、Ding等はこの方式です。何処で注入するかと場所に注目して指定する方法と、何が注入されるかに注目する指定する方法、の2つとも言えないでしょうか。6

  1. InjectorとAnnotation以外は全てAuraのコンポーネントです。Configだけ一部機能追加してますが他のクラスはAura.Diそのままです。 []
  2. 現在プロパティインジェクションは実装されていません []
  3. 引き数を順番ではなく変数名で指定 []
  4. $host, $id, $passを引き数に取るようなコンストラクタでgetInstance($class, array(‘host’ => $host);と指定すると$id, $passはデフォルトの値で$hostだけを指定できます。 []
  5. Guiceでこのように表現されてました []
  6. 個人的には前者はコンテナやコンパイルなど実装の都合から生まれた方法で、後者はインターフェイス指向をより意識した方法ではないかと思うのですがどうでしょうか []