2012年9月10日月曜日

PHPのinterfaeを使った簡単なDiContainerを考えた

性懲りもなく、こんなことを考えてます。

DIコンテナは便利なのですが、設定を作るのが面倒です。
クラスのコード内に、必要なオブジェクトを記述して、コンテナがよしなに注入してくれる方法が楽そうです。

BEAR.SundayのDIモジュールがまさにそうです。

ただアノテーションの処理にDoctrineが必要など、それはそれで面倒な感です。そこで、PHPのインターフェースを使った簡単Diコンテナについて考えてみました。

簡単な流れは:
  1. コンテナ内で、オブジェクトを生成。
  2. オブジェクトのインターフェースを検索。
  3. インターフェース名から注入するクラスを決定。
  4. オブジェクトを生成して、適当なメソードに注入。

具体例です。
namespace Database {
  interface InjectDbaInterface {}
  class Dba {...}
}
namespace Invoice {
  class Billing implements \Database\InjectDbaInterface {
    function injectDba( $Dba ) {
      $this->Dba = $Dba;
    }
  }
  $bill = $container( 'Billing' ); // 注入済みのオブジェクト
}
ポイントは:
  • 必要とされる(であろう)Dbaのクラスと同じnamespaceに、注入用のinterfaceを前もって作っておく。
  • インターフェース名は、\name\space\Inject{クラス名}Interface、と決めておくことで、注入するクラスとネームスペースを特定できる。
  • Invoiceのクラス宣言で、注入してほしいオブジェクトのinterfaceを実装(implements)する。
なお、コンテナ側のコードは、こんな感じ。
if( $interfaces = class_implements( $object ) )
foreach( $interfaces as $interface ) { // インターフェース一覧
  if( preg_match( '/^(.*)Inject([_a-zA-Z0-9]+)Interface$/i',
    $interface, $matches ) ) {
    $className = $matches[1] . $matches[2]; // 注入するクラス
    $injector  = "inject" . $matches[2]; // 注入方式は決め打ち
    $injObj = new $className;
    $object->$injector( $injObj );
  }
}
非常に簡単に動く(はず)。
【追記:2012年9月15日】
簡単なサンプルコードを書きました(https://gist.github.com/3725826)
確かに簡単に動きました。

インターフェースを使って依存性の自動解決をするコンテナがないか気になったのですが、ちょっと探したところでは見当たりません。見つけたのはアノテーションを使っているものばかりでした。(例:Seasarの自動バインディング