SPL: RecursiveIterator

Date
2007-10-29 (Mon)
Category
php

PHP5 からは、Standard PHP Library (SPL) 関数 というのが標準でついてきます。標準 PHP ライブラリとかいったら、すぐに使える便利なライブラリなどを想像してしまう僕はおバカさんかな…標準 Interface / Design Pattern 集とでも云ってくれたほうが、僕にはわかりやすいかったかも。実際の所、ほとんどのコードは自分で実装しなければならない。ただ例えば、今回扱うような foreach の挙動をクラスの側から操作できる、というのはなかなか魅力的だし、今までの PHP にはなかった事ですね。

でも使っている人が少ないのか、コードサンプルがあまり見当たりません。今回実装した RecursiveIterator など、一度ハマりどころがわかれば全然簡単なものの、わからないときはさっぱりわからないもの。いわゆる Design Pattern 集に出てくるような内容なので、晒すのもすこし気後れしますが、まぁ何かの足しにはなるでしょう。というわけで以下コード。

コード

FooNode.php

<?php

class FooNode implements RecursiveIterator { public $name; protected $parent = null; protected $children = array(); protected $cursor = 0;
public function __construct ($n = null) { $this->name = (String)$n; }
public function __toString () { $prefix = null; if (!is_null($this->parent)) { $prefix = (String)$this->parent . '/'; } return $prefix . $this->name; }
public function setParent (FooNode $v) { $this->parent = $v; }
public function add (FooNode $v) { $this->children[ $v->name ] = $v; $v->setParent($this); return $v; }
//Iterator public function current () { if ($this->key() !== false) { return $this->children[$this->key()]; } else { return null; } }
public function key () { $keys = array_keys($this->children); $length = count($keys); if (($length === 0) || ($length <= $this->cursor)) { return false; } return $keys[$this->cursor]; }
public function next () { if (count($this->children) > 0) { $this->cursor = $this->cursor + 1; } }
public function rewind () { $this->cursor = 0; }
public function valid () { $result = false; if ($this->current() instanceof FooNode) { $result = true; } return $result; }
// RecursiveIterator public function getChildren () { return $this; }
public function hasChildren () { //trigger_error($this->name . " hasChildren?"); return !empty($this->children); }
// static function public static function traversal (FooNode $f, $d = 0) { $count = 0; foreach ($f as $node) { //$prefix = str_repeat("", $d); //echo $prefix; echo $node; echo "\n";
if ($node->hasChildren()) { $count += FooNode::traversal($node->getChildren(), ($d + 1)); } ++$count; } return $count; }
}

main.php

<?php

require_once('FooNode.php');
$a = new FooNode('root');
$b = $a->add(new FooNode('hoge')); $b->add(new FooNode(0)); $b->add(new FooNode(1)); $b->add(new FooNode(2));
$c = $a->add(new FooNode('dims')); $d = $a->add(new FooNode('dinges'));
$e = $c->add(new FooNode(3)); $e->add(new FooNode('a')); $e->add(new FooNode('b')); $e->add(new FooNode('c')); $c->add(new FooNode(4)); $c->add(new FooNode(5));
$f = $d->add(new FooNode(6)); $f->add(new FooNode('d')); $f->add(new FooNode('e')); $f->add(new FooNode('f')); $d->add(new FooNode(7)); $d->add(new FooNode(8));
$c = FooNode::traversal($a);
echo "$c times traversed.\n";

使い方

同じディレクトリに main.php と FooNode.php というファイルを用意して、コマンドラインから php main.php とタイプしてみてください。

簡単な解説

FooNode::traversal() というのが、Recursive Iteration をするメソッドです。僕は getChildren と hasChildren が返す値について少々悩みました。hasChildren は Loop の外側から見ると、子供の子供があるかどうかを確認するメソッドですが、この実装では(RecusiveIterator ではなく)Iterator がオブジェクトの子要素を iterate するようになっているため、hasChildren が呼び出されるのは既に Loop が回っている先。だから直近の子供を確認しているのですね。

ただまぁこの traversal メソッド内の if 文で hasChildren なのはちょっとダサい気がするので、外部 iterator を使った実装に変えた方がいいかもなぁ。でも実際には db が絡むからこれでのいいのかもしれない。

あと Iterator::next() や Iterator::current() が 配列関数の next()reset() とは少し挙動が違います。僕が前に Iterator の実装をした時は同じようにしていたのですが、今回 http://www.phpguru.org/downloads/Tree/Tree-2.0.0.tgz/Tree-2.0.0/ を見つけてこのようにしました。

今よく Manual を見直すと void RecursiveDirectoryIterator::next (void)void RecursiveDirectoryIterator::rewind(void) とあるので、僕が間違っていたんですね…

また時間を見つけて SPL についても書いてみたいと思っています。

Comment:0

Comment Form

Remember Me?


Trackback:0

TrackBack URL for this entry
http://blogs.grf-design.com/mt/mt-tb.cgi/238
Listed below are links to weblogs that reference
SPL: RecursiveIterator from The Croton

Return to Page Top