PHP5.3の__callStaticをエミュレートする

PHP 5.3.0 ではマジックメソッド __callStatic が追加された。グローバルに使用されるユーティリティクラスで、動的にメソッド名を生成したいときに便利。

class FooUtil {
  static protected $_inc = array('test1' => 1,
				 'test2' => 2);
 
  public static function __callStatic($method, $args) {
    return isset(static::$_inc[$method])
      ? static::$_inc[$method] + $args[0] : $args[0];
  }
}
echo FooUtil::test1(100); // => 101
echo FooUtil::test2(100); // => 102
echo FooUtil::testxxx(100); // => 100

同等の機能を PHP 5.2 より前の環境で再現したい場合、シングルトンパターンと __call を組み合わせるのが最も普通のやり方だと思う。

class FooUtil2 {
  protected $_inc = array('test1' => 1,
			  'test2' => 2);
  static private $__singleton = null;
 
  public static function getInstance() {
    if(self::$__singleton === null) {
      self::$__singleton = new self;
    }
    return self::$__singleton;
  }
 
  public function __call($method, $args) {
    return isset($this->_inc[$method])
      ? $this->_inc[$method] + $args[0] : $args[0];
  }
}
echo FooUtil2::getInstance()->test1(100); // => 101
echo FooUtil2::getInstance()->test2(100); // => 102
echo FooUtil2::getInstance()->testxxx(100); // => 100

この方法の欠点は「呼び出し時のコードが長い」ことである。そこで個人的にはクラス名と同じ名前のグローバル関数を使う方法を提案したい。

function FooUtil3() {
  static $singleton = null;
  if($singleton === null) {
    $singleton = new FooUtil3();
  }
  return $singleton;
}
 
class FooUtil3 {
  protected $_inc = array('test1' => 1,
			  'test2' => 2);
 
  public function __call($method, $args) {
    return isset($this->_inc[$method])
      ? $this->_inc[$method] + $args[0] : $args[0];
  }
}
echo FooUtil3()->test1(100); // => 101
echo FooUtil3()->test2(100); // => 102
echo FooUtil3()->testxxx(100); // => 100

この方法ならば __callStatic を使うのと比べて2文字長いだけだ。しかも他2つの方法よりもカプセル化の面では優れている。関数とクラスは「名前が同じ」というごく緩やかなつながりを持っているだけなので、静的な意味でも動的な意味でも、あとから自由に実装を切り替えられる。staticメソッドでなければならない、シングルトンでなければならない、といった制約はない。

これはグローバル変数や単純なレジストリ(CakePHPにおけるConfigureのような)を使うのとも違う。関数なので、どんな処理でも差し挟むことができる。関数の方がパラメータを持ってもいい。

FooUtil4($context)->test($parameters);

グローバル関数というと問題になるのは名前の衝突だが、関数名とクラス名では名前の傾向が異なるので、クラス名が衝突しないのであれば同じ名前の関数も衝突しない可能性が高いと考えられる。

あまり公のプロジェクトで採用する気にはならないが、内々に使うにはそれなりに便利なテクニックだと思う。

Leave a Reply