Locked History Actions

php/DI

せめてこれぐらいはやってほしいDI

DIというよりもサービスロケータですね、これは。 ともあれ、Springやシーサーのように(もともとは)貧弱な言語仕様のもとで無理やりDIを行おうとすると、その仕組み自体が面倒すぎる。 現在のphpの言語仕様の中で、「コメントの中にアノテーションを記述する」だの、「XMLを使ってワイヤリングする」だのするとかえって面倒なので、こういう場合は素直にサービスロケータを使った方がよい。 しかし「PHP サービスロケータ」としても全く検索結果が現れないのはどういうわけだろうか。

di.inc

<?php
/**
 * せめてこれぐらいはやれDependency Injectionもどき
 * 
 * インターフェース(抽象クラス含む)に、実装クラスあるいはインスタンスをバインドしておく。
 * インターフェースが指定されたら、バインドされたインスタンスか、あるいは実装クラスから
 * インスタンスを作成して返す。
 * phpでは「クラス」自体を指定することができないらしいので、「クラス」の代わりに「クラス名」を指定する。
 * 
 * なお、バインディングを入れ替えられるようにしてあるが、これは必要ないかも。
 */
class DI {

  /** バインディング */
  public $bindings = array();

  /** デフォルトDIオブジェクト */  
  static private $current;
    
  /** 現在のDIオブジェクトを取得する */
  public static function getCurrent() {
    if (!self::$current) {
      self::$current = new DI();
    }
    return self::$current;
  }
  
  /** 現在のDIオブジェクトを設定する */
  private static function setCurrent($value) {
    assert(get_class($value) == "DI");
    self::$current = $value;
  }
  
  /** カレントのDIオブジェクトにインターフェース名とインスタンスのバインドを設定する */
  static public function bindInst($interface, $inst) {
    assert(is_string($interface));
    assert(is_object($inst));
    self::getCurrent()->bindings[$interface] = $inst;
  }
  
  /** カレントのDIオブジェクトにインターフェース名とクラス名のバインドを設定する */
  static public function bindClass($interface, $class) {
    assert(is_string($interface));
    assert(is_string($class));
    self::getCurrent()->bindings[$interface] = $class;
  }

  /** 
   * インターフェース名からそのインスタンスを取得する
   * インスタンスがバインドされている場合はそのインスタンスを返す。
   * クラスがバインドされている場合はそのクラスのインスタンスを生成して返す。
   */
  static public function get($interface) {
    $current = self::getCurrent();
    @$binding = $current->bindings[$interface];
    
    // インスタンスがバインドされている場合、それを返す。
    if (is_object($binding)) {
      return $binding;
    }
    
    // 文字列がバインドされている場合、それをクラス名として新たなインスタンスを生成して返す。
    if (is_string($binding)) {
      return new $binding();
    }
    
    print "Binding of $interface not found";
    assert(false);
  }  
}

?>

上のテスト

<?php
include "di.inc";

interface Test {
  public function test();  
}
class TestImpl implements Test {
  public function test() {
    return 1;
  }
}
class TestMock1 implements Test {
  public function test() {
    return 2;
  }
}
class TestMock2 implements Test {
  public $value = 3;
  public function test() {
    return $this->value;
  }   
}

DI::bindClass('Test', 'TestImpl');
$test = DI::get('Test');
assert($test->test() == 1);

DI::bindClass('Test', "TestMock1");
$test = DI::get('Test');
assert($test->test() == 2);

$inst = new TestMock2();
DI::bindInst('Test', $inst);
assert(DI::get('Test')->test() == 3);
$inst->value = 4;
assert(DI::get('Test')->test() == 4);


?>

GuiceのImplementedBy風に使うには以下。

DI::bindClass('Test', 'TestImpl')
interface Test {
}
class TestImpl implements Test {
}