いけむランド

はてダからやってきました

PHP 5.2.x でできるだけクロージャっぽいことをする

create_function を使ったら負けかなと思ってる。


リフレクションとかしないのにシンボルが文字列として扱えたりする挙動がコンパイラ言語出身者としてはかなりの気持ち悪さを感じることで有名な PHP ですが、足掻いてみました。

しかし、function(args){...} や内部クラスは書けない時点でインラインに関数を置くのは無理。→ 何処かグローバルなところにわざわざ関数を定義しないといけない。という制約からは逃れられないみたいです。

グローバルにところに置かないといけないということはクロージャの引数以外に使いたい局所変数も一旦、大域変数にしないといけないということになるため、さらに面倒。><

というわけでかろうじて思いつくのはクラスをつくって、静的メンバに必要な変数を囲い込んで、グローバルな名前空間の汚染を妨げるくらいです。


そもそもなんでこんなことを悩みだしたかと言うと、DB からデータをとってきて、それをユーザが指定した順番に並べるような処理をしたかったわけです。(以下のサンプルコードの $team は本当は DB から取得して、$order は POST で渡されてくるようなシチュエーションです。)

<?php
class Superman
{
  private $name;
  private $power;
  public function __construct($name, $power)
  {
    $this->name  = $name;
    $this->power = $power;
  }
  public function getName()  { return $this->name;  }
  public function getPower() { return $this->power; }
}

$team = array(new Superman('フェニックス', 100000000),
	      new Superman('オメガマン',    86000000),
	      new Superman('マンモスマン',  78000000),
	      new Superman('プリズマン',    52000000),
	      new Superman('サタンクロス',  41000000));

$order = array('サタンクロス',
	       'プリズマン',
	       'マンモスマン',
	       'オメガマン',
	       'フェニックス');
?>


PHP 5.3 だと簡単に書けます。

<?php
usort($team, function($a, $b) use($order) {
    if (array_search($a->getName(), $order)
	> array_search($b->getName(), $order)) {
      return 1;
    } else if (array_search($a->getName(), $order) 
	       < array_search($b->getName(), $order)) {
      return -1;
    } else {
      return 0;
    }
  });
}
?>


PHP 5.2 で足掻いてみたのが以下のとおり。

<?php
class Closuremock
{
  private static $used = array();
  public static function using($funcname)
  {
    $args = func_get_args();
    array_shift($args);
    self::$used[$funcname] = $args;
  }
  public static function compare(Superman $a, Superman $b)
  {
    list($order) = self::$used[__FUNCTION__];
    if (array_search($a->getName(), $order)
	> array_search($b->getName(), $order)) {
      return 1;
    } else if (array_search($a->getName(), $order) 
	       < array_search($b->getName(), $order)) {
      return -1;
    } else {
      return 0;
    }
  }
}

Closuremock::using('compare', $order);
usort($team, array('Closuremock', 'compare'));
?>


結局、静的メンバに引数以外で使用する変数を関数名をキーにした連想配列に放り込んでおくだけ。小一時間ほど頑張って悩んで書いてみたけど、後から見ると大したことないなあ。(苦笑)


それにしても便利な機能が使えないとをわかると、途端に不便に感じるなあ。