イテレーション

イテレータ/「Iterator」インターフェイス

配列やオブジェクトのプロパティは、「foreach文」によって反復(イテレーション)して走査する事が出来ましたが、これは「foreach文」が“「コレクション」を走査する機能”を持っていたからです。一方「for文」や「while文」は同じ反復文であってもそのような機能を持たないため走査が出来ません。「コレクション」とは、配列やオブジェクト(のプロパティ)の様な、“要素の集まりを持つ構造体”の事です。

“走査する機能”の具体的な内容は、配列の内部ポインタ(現在配列の何処を指しているかを示すもの)の移動や、ポインタが指す要素の有効性(ポインタが指す位置に要素があるか否か)のチェック等です。「foreach文」で配列を走査しようとすると、まずポインタが先頭要素を指すように初期化し、ループが開始されます。ループごとに現在ポインタが指す要素や添え字を返し、ポインタを1つ先の要素に移動し、移動先に要素があるか否かをチェック、無い(配列の要素数を超えた)場合にループから抜けます。これら全てを「foreach文」は自動的に実行してくれるのです。「for文」や「while文」で配列の走査を行う場合は、ポインタの移動を行う関数等を使用する必要があります。

ここで疑問が。オブジェクトの場合はどうやって走査しているのか。それは、アクセス可能なプロパティへのリファレンスを何処かの配列に格納し、その配列に対して走査を行っているんです。多分(オイ)。

配列やオブジェクトの走査は「foreach文」が持つ“走査する機能”によって行われますが、この“走査する機能”が機能するのは、配列やオブジェクトが“走査される能力”を持っているからです。で、この配列やオブジェクトが持つ“走査される能力”を「イテレータ」と呼びます。この「イテレータ」は「Iterator」インターフェイス(「foreach文」によって走査される「イテレータ」としての機能を備える)として定義されていて、この定義済みインターフェイスを(ちゃんと機能するように)実装して、自作の「イテレータ」を作る事が出来ます。

「Iterator」インターフェイスを実装したクラスのオブジェクトを「foreach文」で走査しようとすると、そのオブジェクトが持つ「イテレータ」が実行されるようになります(各種メソッドが丁度“特殊メソッド”の様に自動的にコールされる)。

当然、「Iterator」インターフェイスを実装する際は、各種メソッドが「イテレータ」として機能するように処理部を実装しないと、正常に機能しません。

以下のサンプルと結果をご覧下さい。

<pre>
<?php
    #「Iterator」インターフェイスで定義されるメソッド名を出力する
    print_r(get_class_methods("Iterator"));
    
    echo "<hr />";
    
    #「Iterator」インターフェイスを実装する
    abstract class MyIterator implements Iterator{
        private $arr = array();
        protected function iterator_setting($vars){
            $this->arr = $vars;
        }
        public function rewind(){
            echo "****** rewind() ******\n";
            reset($this->arr);
        }
        public function key(){
            echo "****** key() ******\n";
            return key($this->arr);
        }
        public function current(){
            echo "****** current() ******\n";
            return current($this->arr);
        }
        public function next(){
            echo "****** next() ******\n";
            next($this->arr);
        }
        public function valid(){
            echo "****** valid() ******\n";
            return !is_null(key($this->arr));
        }
    }
    
    #「イテレータ」を継承したクラスを定義する
    class HODE extends MyIterator{
        public    $pub_var = "public_property";
        protected $pro_var = "protected_property";
        private   $pri_var = "private_property";
        public function __construct(){
            $this->iterator_setting(get_object_vars($this));
        }
    }
    
    #普通のクラスを定義する
    class NASU{
        public    $pub_var = "public_property";
        protected $pro_var = "protected_property";
        private   $pri_var = "private_property";
    }
    
    #「イテレータ」を備えないオブジェクトのイテレーション
    foreach(new NASU as $name => $val){
        echo "--- begin the loop ---\n";
        echo "{$name} : {$val}\n";
        echo "--- end the loop ---\n\n";
    }
    
    echo "<hr />";
    
    #「イテレータ」を備えるオブジェクトのイテレーション
    foreach(new HODE as $name => $val){
        echo "\n--- begin the loop ---\n";
        echo "{$name} : {$val}\n";
        echo "--- end the loop ---\n\n";
    }
?>
</pre>
Array
(
    [0] => current
    [1] => next
    [2] => key
    [3] => valid
    [4] => rewind
)

--- begin the loop --- pub_var : public_property --- end the loop ---
****** rewind() ****** ****** valid() ****** ****** current() ****** ****** key() ****** --- begin the loop --- pub_var : public_property --- end the loop --- ****** next() ****** ****** valid() ****** ****** current() ****** ****** key() ****** --- begin the loop --- pro_var : protected_property --- end the loop --- ****** next() ****** ****** valid() ****** ****** current() ****** ****** key() ****** --- begin the loop --- pri_var : private_property --- end the loop --- ****** next() ****** ****** valid() ******

「Iterator」インターフェイスを実装した「MyIterator」クラス(インスタンスを生成しないので「抽象クラス」にした)を定義し、「プロパティ$arr」に、派生クラスのインスタンスが持つ全てのプロパティを配列の形で格納するようにしました。「MyIterator」クラスの派生クラス「HODE」クラスを定義し、コンストラクタ内の「$this->iterator_setting(get_object_vars($this));」によって全プロパティを連想配列にして「MyIterator」クラスの「プロパティ$arr」にセットします。「イテレータ」はこのプロパティ(配列)に対して走査を行います。「Iterator」インターフェイスを実装した「MyIterator」クラスを継承した「HODE」クラスは、「イテレータ」を自身の機能として備えるクラスになります(汗)。

「Iterator」は下表に記した5つのメソッドを備えます。各メソッドは「イテレータ」として機能するように実装する必要があります。

「Iterator」が備えるメソッドの機能と返り値の型
メソッド機能返り値の型
rewind(void) 配列の内部ポインタが先頭を指すように初期化する void
key(void) ポインタが指す位置の添え字を返す string
current(void) ポインタが指す要素を返す mixed
next(void) ポインタを1つ先に移動する void
valid(void) ポインタが指す位置の有効性をチェックする boolean

サンプルの結果を見ると、「Iterator」インターフェイスを実装している方のオブジェクトでは各種メソッドが自動的にコールされているのが解かります。処理の流れを追うと、以下のようになっています。

  1. 「メソッドrewind()」でポインタ初期化(最初に1回だけコールされる)。
  2. 「メソッドvalid()」でポインタの有効性のチェック(1回目)。
  3. ポインタが有効でループ処理(1回目)。
  4. 「メソッドcurrent()」でポインタが指す要素をゲット(1回目)。
  5. 「メソッドkey()」でポインタが指す位置の添え字をゲット(1回目)。
  6. 「メソッドnext()」でポインタを1つ先に移動(1回目)。
  7. 「メソッドvalid()」(2回目)。
  8. ポインタが有効でループ処理(2回目)。
  9. 「メソッドcurrent()」(2回目)。
  10. 「メソッドkey()」(2回目)。
  11. 「メソッドnext()」(2回目)。
  12. 「メソッドvalid()」(3回目)。
  13. ポインタが有効でループ処理(3回目)。
  14. 「メソッドcurrent()」(3回目)。
  15. 「メソッドkey()」(3回目)。
  16. 「メソッドnext()」(3回目)。
  17. 「メソッドvalid()」(4回目)。
  18. ポインタが無効でループ終了。

「イテレータ」のメソッドは、「foreach文」においては適宜自動的にコールされます。当然、手動でコールすることも可能なので、「for文」や「while文」でも機能させることが出来ます。以下のサンプルをご覧下さい。

<pre>
<?php
    abstract class MyIterator implements Iterator{
        ...省略
    }
    
    class HODE extends MyIterator{
        ...省略
    }
    
    for($hode = new HODE, $hode->rewind();
                                      $hode->valid(); $hode->next()){
        $val  = $hode->current();
        $name = $hode->key();
        echo "\n--- begin the loop ---\n";
        echo "{$name} : {$val}\n";
        echo "--- end the loop ---\n\n";
    }
    
    echo "<hr />";
    
    $hode = new HODE;
    
    $hode->rewind();
    
    while($hode->valid()){
        $val  = $hode->current();
        $name = $hode->key();
        echo "\n--- begin the loop ---\n";
        echo "{$name} : {$val}\n";
        echo "--- end the loop ---\n\n";
        $hode->next();
    }
?>
</pre>

結果は前回のサンプルと全く同じです。今回のサンプルでは、全てのメソッドを手動でコールしています。

ついでに、以下のように実装すると、リファレンスを受け取れるようになります。

<pre>
<?php
    abstract class MyIterator implements Iterator{
        ...省略
        public function &current(){
            echo "****** current() ******\n";
            return $this->arr[key($this->arr)];
        }
        ...省略
    }
    
    class HODE extends MyIterator{
        ...省略
    }
    
    $hode = new HODE;
    
    foreach($hode as &$val){
        $val = "HODENASU!!!";
    }
    
    unset($val);
    
    foreach($hode as $name => $val){
        echo "\n--- begin the loop ---\n";
        echo "{$name} : {$val}\n";
        echo "--- end the loop ---\n\n";
    }
?>
</pre>
(一部省略しています)

--- begin the loop ---
pub_var : HODENASU!!!
--- end the loop ---

--- begin the loop ---
pro_var : HODENASU!!!
--- end the loop ---

--- begin the loop ---
pri_var : HODENASU!!!
--- end the loop ---

「プライベート」だろうが何だろうが書き換え可能です。嗚呼、無意味。

と、ここまでのサンプルでは「HODE」クラスを作って、継承した「イテレータ」の機能で「HODE」クラスのオブジェクトのプロパティを走査してましたが、単にオブジェクトのプロパティを走査するサンプルとして作っただけのクラスなので、必ずしも必要ありません。「イテレータ」は自身の内部配列(「イテレータ」が持つプロパティに格納した配列)の走査時の振る舞いを定義したもので、「イテレータ」は単にこの内部配列を走査するだけなので、配列の形に形状を変えて内部配列に格納すれば、それを走査します。要するに、内部配列がセットされさえすれば、単独で使用することも出来ます。

多分、解説の順番間違えました(何)。以下のサンプルと結果をご覧下さい。

<pre>
<?php
    #「イテレータ」の実装
    class MyIterator implements Iterator{
        private $arr;
        public function __construct($item = null){
            if(is_array($item))
                $this->arr = $item;
            else if(is_object($item))
                $this->arr = get_object_vars($item);
            else if(is_string($item))
                $this->arr = explode("\n", $item);
            if(!is_array($this->arr))
                $this->arr = array();
        }
        public function rewind(){
            reset($this->arr);
        }
        public function key(){
            return key($this->arr);
        }
        public function &current(){
            return $this->arr[key($this->arr)];
        }
        public function next(){
            next($this->arr);
        }
        public function valid(){
            return !is_null(key($this->arr));
        }
    }
    
    function iteration($item){
        echo "****** begin iteration() ******\n\n";
        foreach(new MyIterator($item) as $key => $val){
            echo "--- begin the loop ---\n";
            echo "{$key} : {$val}\n";
            echo "--- end the loop ---\n\n";
        }
        echo "****** end iteration() ******\n\n";
    }

    class HODE{
        public    $pub_var = "public_property";
        protected $pro_var = "protected_property";
        private   $pri_var = "private_property";
    }
    
    iteration(new HODE);
    iteration(array("HODENASU1", "hode" => "HODENASU2", "HODENASU3"));
    iteration("HODENASU1\nHODENASU2\nHODENASU3");
?>
</pre>
****** begin iteration() ******

--- begin the loop ---
pub_var : public_property
--- end the loop ---

****** end iteration() ******

****** begin iteration() ******

--- begin the loop ---
0 : HODENASU1
--- end the loop ---

--- begin the loop ---
hode : HODENASU2
--- end the loop ---

--- begin the loop ---
1 : HODENASU3
--- end the loop ---

****** end iteration() ******

****** begin iteration() ******

--- begin the loop ---
0 : HODENASU1
--- end the loop ---

--- begin the loop ---
1 : HODENASU2
--- end the loop ---

--- begin the loop ---
2 : HODENASU3
--- end the loop ---

****** end iteration() ******

今回のサンプルでは、「MyIterator」クラスのコンストラクタ内で、渡された引数を型チェック後配列にして内部配列に格納しましたが、「MyIterator」クラスを継承して、どんな値が渡されるかを想定した各種用途別「イテレータ」を作る事も出来ます。

集約クラス(コンポジション)/「IteratorAggregate」インターフェイス

クラス定義において、「イテレータ」を自身に実装するか、もしくは「イテレータ」を実装したクラスを継承した場合は、そのクラス自身の機能として「イテレータ」を備える事になります。要するに、「Iterator」インターフェイスを実装するか、実装したクラスを継承したクラスは「イテレータ」になります。

これとは違って、「イテレータ」を実装したクラスのオブジェクトをメンバに持ったりする(プロパティに「イテレータ」オブジェクトを格納するか、「イテレータ」オブジェクトを返す機能を持つ)クラスは、自身の機能としてではなく、メンバの機能として「イテレータ」を備える事になります。要するに、自身は「イテレータ」ではなく、「イテレータ」を持つクラスになります。

で、クラスが「イテレータ」を備えるなら、前者の関係を“「クラス is-a イテレータ」の関係”と呼び、後者の関係を“「クラス has-a イテレータ」の関係”または“「イテレータ part-of クラス」の関係”と呼びます。要するに、“自身が「イテレータ」である”か、“自身は「イテレータ」を持っている(「イテレータ」は自身の一部)”という関係の違いです。

で、“自身が何たらである”というクラスに対して、“自身は何たらを持っている”というクラス(別のクラスのオブジェクトをメンバにもつクラス)を特に「集約クラス(コンポジション)」と呼びます(と、思う・・・)。

「集約クラス版イテレータ」は定義済みインターフェイス「IteratorAggregate」を実装して作成します。このインターフェイスを実装すると、「Iterator」インターフェイスを実装したクラスが“「イテレータ」である”と見なされたように、“「イテレータ」を持っている”と見なされるようになります。

「IteratorAggregate」インターフェイスを実装したクラスのオブジェクトを「foreach文」で走査しようとすると、「イテレータ」オブジェクトを要求し、渡した「イテレータ」オブジェクトが持つ「イテレータ」が実行されます。

これまでと違うのは、自身が持つ「イテレータ」機能が使われるのではなく、「イテレータ」機能を持つオブジェクトを渡し、そのオブジェクトが持つ「イテレータ」機能が使われる点です。

以下のサンプルと結果をご覧下さい。

<pre>
<?php
    #「IteratorAggregate」インターフェイスで定義されるメソッド名を出力する
    print_r(get_class_methods("IteratorAggregate"));
    
    echo "<hr />";
    
    #「イテレータ」を実装する
    class MyIterator implements Iterator{
        private $arr;
        public function __construct($arr){
            $this->arr = $arr;
        }
        public function update($arr){
            $this->arr = $arr;
        }
        public function rewind(){
            echo " * MyIterator::rewind() *\n";
            reset($this->arr);
        }
        public function key(){
            return key($this->arr);
        }
        public function &current(){
            return $this->arr[key($this->arr)];
        }
        public function next(){
            next($this->arr);
        }
        public function valid(){
            return !is_null(key($this->arr));
        }
    }
    
    #「IteratorAggregate」インターフェイスを実装する
    class HODE implements IteratorAggregate{
        private $iterator;
        private $first_items;
        private $items;
        public function __construct($vars = array()){
            $this->first_items = $this->items = $vars;
            $this->iterator = new MyIterator($this->items);
        }
        public function getIterator(){
            echo "****** getIterator() ******\n";
            return $this->iterator;
        }
        public function add($add_item){
            array_push($this->items, $add_item);
            $this->iterator->update($this->items);
        }
        public function reset(){
            $this->items = $this->first_items;
        }
    }
    
    function iteration(&$obj){
        echo "------ begin iteration() ------\n";
        foreach($obj as $key => $val){
            echo str_pad($key, 4), " : {$val}\n";
        }
        echo "------ end iteration() ------\n\n";
    }
    
    iteration(new HODE);
    
    $arr = array("HODENASU", "hode" => "NDEGASU", "AFODEGASU");
    
    $hode = new HODE($arr);
    
    $hode->add("NDA1");
    $hode->add("NDA2");
    $hode->add("NDA3");
    
    iteration($hode);
    
    $hode->reset();
    $hode->add("APETOPE");
    
    iteration($hode);
?>
</pre>
Array
(
    [0] => getIterator
)

------ begin iteration() ------ ****** getIterator() ****** * MyIterator::rewind() * ------ end iteration() ------ ------ begin iteration() ------ ****** getIterator() ****** * MyIterator::rewind() * 0 : HODENASU hode : NDEGASU 1 : AFODEGASU 2 : NDA1 3 : NDA2 4 : NDA3 ------ end iteration() ------ ------ begin iteration() ------ ****** getIterator() ****** * MyIterator::rewind() * 0 : HODENASU hode : NDEGASU 1 : AFODEGASU 2 : APETOPE ------ end iteration() ------

「Iterator」インターフェイスを実装した「MyIterator」クラスを定義。「IteratorAggregate」インターフェイスを実装した「HODE」クラスを定義し、コンストラクタ内の「$this->iterator = new MyIterator($this->items);」によって、コンストラクタに渡された引数(配列)を格納した「プロパティ$items」を引数に指定して「MyIterator」クラスのオブジェクト(「イテレータ」オブジェクト)を生成し、「プロパティ$iterator」に格納します。

「HODE」クラスには、「プロパティ$items」に要素を追加する「add()」メソッドと、配列構造をオブジェクト生成時の状態に戻す「reset()」メソッドを持たせました。

「IteratorAggregate」は下表に記した1つのメソッドを備えます。

「IteratorAggregate」が備えるメソッドの機能と返り値の型
メソッド機能返り値の型
getIterator(void) 「イテレータ」オブジェクトを返す Iterator

サンプルの結果を見ると、「IteratorAggregate」インターフェイスを実装している「HODE」クラスのオブジェクトを「foreach文」で走査しようとすると、まず「getIterator()」メソッドが自動的にコールされ、その後に「イテレータ」オブジェクトの「rewind()」メソッドがコールされているのが解かります。つまり、「getIterator()」メソッドで返された「イテレータ」オブジェクトの「イテレート」機能が使用されます。

「Traversable」インターフェイス

ところで「インターフェイス」のページで、幾つかのインターフェイスが予め定義されているのを見ましたが、改めてその定義済みインターフェイスの名前とメソッド名を下記のスクリプトで見てみたいと思います。

<pre>
<?php
    #定義済みインターフェイスと定義されるメソッド名を取得
    foreach(get_declared_interfaces() as $interface_name)
        $interfaces[$interface_name] = get_class_methods($interface_name);
    
    print_r($interfaces);
?>
</pre>
Array
(
    [Traversable] => Array
        (
        )

    [IteratorAggregate] => Array
        (
            [0] => getIterator
        )

    [Iterator] => Array
        (
            [0] => current
            [1] => next
            [2] => key
            [3] => valid
            [4] => rewind
        )

    [ArrayAccess] => Array
        (
            [0] => offsetExists
            [1] => offsetGet
            [2] => offsetSet
            [3] => offsetUnset
        )

    [Reflector] => Array
        (
            [0] => export
            [1] => __toString
        )

    [RecursiveIterator] => Array
        (
            [0] => hasChildren
            [1] => getChildren
            [2] => current
            [3] => next
            [4] => key
            [5] => valid
            [6] => rewind
        )

    [SeekableIterator] => Array
        (
            [0] => seek
            [1] => current
            [2] => next
            [3] => key
            [4] => valid
            [5] => rewind
        )
)

現在定義されている7つのインターフェイスの内、「Traversable」と「IteratorAggregate」、「Iterator」、「RecursiveIterator」、「SeekableIterator」の5つが「イテレータ」に関係するインターフェイスです。これら5つのインターフェイスを実装してみます。以下のサンプルと結果をご覧下さい。

<pre>
<?php
    #「イテレータ」に関係する定義済みインターフェイスを実装する
    /*
    class MyTraversable implements Traversable{
        //
    }
    */
    class MyIteratorAggregate implements IteratorAggregate{
        public function getIterator(){}
    }
    class MyIterator implements Iterator{
        public function rewind(){}
        public function key(){}
        public function current(){}
        public function next(){}
        public function valid(){}
    }
    class MyRecursiveIterator implements RecursiveIterator{
        public function rewind(){}
        public function key(){}
        public function current(){}
        public function next(){}
        public function valid(){}
        public function hasChildren(){}
        public function getChildren(){}
    }
    class MySeekableIterator implements SeekableIterator{
        public function rewind(){}
        public function key(){}
        public function current(){}
        public function next(){}
        public function valid(){}
        public function seek($arg){}
    }
    
    $imp_arr["MyIterator"]          = class_implements(new MyIterator);
    $imp_arr["MyIteratorAggregate"] = class_implements(new MyIteratorAggregate);
    $imp_arr["MyRecursiveIterator"] = class_implements(new MyRecursiveIterator);
    $imp_arr["MySeekableIterator"]  = class_implements(new MySeekableIterator);
    
    print_r($imp_arr);
?>
</pre>
Fatal error:インターフェイス「Traversable」は
 「IteratorAggregate」か「Iterator」の一部として実装されなければならない)

Array
(
    [MyIterator] => Array
        (
            [Iterator] => Iterator
            [Traversable] => Traversable
        )

    [MyIteratorAggregate] => Array
        (
            [IteratorAggregate] => IteratorAggregate
            [Traversable] => Traversable
        )

    [MyRecursiveIterator] => Array
        (
            [RecursiveIterator] => RecursiveIterator
            [Traversable] => Traversable
            [Iterator] => Iterator
        )

    [MySeekableIterator] => Array
        (
            [SeekableIterator] => SeekableIterator
            [Traversable] => Traversable
            [Iterator] => Iterator
        )
)

各「インターフェイス」を実装し、そのオブジェクトが実装する「インターフェイス」を調べました。

Traversable」インターフェイスを実装しようとすると以下の様なアラートと共に「E_ERROR」エラーを発します。

「Class MyTraversable must implement interface Traversable as part of either Iterator or IteratorAggregate」というアラートを出す。

“インターフェイス「Traversable」は「IteratorAggregate」か「Iterator」の一部として実装されなければならない”って事らしい。

で、結果を見ると、全てのオブジェクトが「Traversable」インターフェイスを実装し、「RecursiveIterator」と「SeekableIterator」を実装したオブジェクトは「Iterator」インターフェイスも実装しているのが判ります。これらから、「イテレータ」に関係する5つの定義済みインターフェイスは、以下の様な継承関係にあると推測出来ます。

Traversable(全探索できる)
      |
      +--- IteratorAggregate(「イテレータ」コンポジション)
      |
      +--- Iterator(「イテレータ」)
               |
               +--- RecursiveIterator(再帰的な「イテレータ」)
               |
               +--- SeekableIterator(シークできる「イテレータ」)

そんなこんなで(どんなこんなで)、「Traversable」インターフェイス自体を実装したクラスを定義することは出来ません。「Traversable」は、「イテレータ」に関係するインターフェイスの頂点に君臨し、これらのインターフェイスを統括する為だけに存在します。

が(え?)、“犬”と“猫”は“動物”でも、“動物”が必ずしも“犬”か“猫”ではないように、「Traversable」が「イテレータ」というわけではありません。「Traversable」は名前が示す通り、“全探索できる”ものの頂点にあるもので、その下には「イテレータ」と関係ないものも存在します。以下のサンプルと結果をご覧下さい。

<pre>
<?php
    #「イテレータ」とは無関係な「Traversable」の子孫の抽出・出力
    foreach(get_declared_interfaces() + get_declared_classes() as $i_c){
        $i_c_obj = new ReflectionClass($i_c);
        #「Traversable」であるが「Iterateable」でないものを選択
        #「Iterateable」は「Iterator/IteratorAggregate」を実装する「イテレータ」
        #「Iterateable」というものが定義されているわけではありません
        if($i_c_obj->implementsInterface("Traversable") &&
                                               !$i_c_obj->isIterateable())
            echo $i_c, "\n";
    }
?>
</pre>
Traversable
SimpleXMLElement

「SimpleXMLElement」クラスは「Traversable」インターフェイスを実装しますが、「イテレータ」とは無関係です。

サンプル中の「ReflectionClass」クラスについては「リフレクション」のページで解説します。

「RecursiveIterator」インターフェイス/「SeekableIterator」インターフェイス

「Iterator」の派生インターフェイスである「RecursiveIterator」と「SeekableIterator」は、名前が示す通り、「RecursiveIterator」は“再帰的な「イテレータ」”で、「SeekableIterator」は“シークできる「イテレータ」”です。

「RecursiveIterator」は「Iterator」に再帰処理を行うような機能を追加した「イテレータ」で、「SeekableIterator」は「Iterator」にシーク機能を追加した「イテレータ」です。

で、「RecursiveIterator」と「SeekableIterator」は、「Iterator」が備えるメソッドの他にそれぞれ下表に記したメソッドを追加で備えます。

「RecursiveIterator」が追加で備えるメソッドの機能と返り値の型
メソッド機能返り値の型
hasChildren(void) ポインタが指す要素が子要素を持つかチェックする boolean
getChildren(void) ポインタが指す要素を引数に「イテレータ」オブジェクトを返す RecursiveIterator
「SeekableIterator」が追加で備えるメソッドの機能と返り値の型
メソッド機能返り値の型
seek(mixed インデックス) ポインタを指定した「インデックス」まで移動する void

以下のサンプルと結果をご覧下さい。

<pre>
<?php
    #「Iterator」を実装する
    class MyIterator implements Iterator{
        protected $arr;
        public function __construct($arr){
            $this->arr = $arr;
        }
        public function rewind(){
            reset($this->arr);
        }
        public function key(){
            return key($this->arr);
        }
        public function &current(){
            return $this->arr[key($this->arr)];
        }
        public function next(){
            next($this->arr);
        }
        public function valid(){
            return !is_null(key($this->arr));
        }
    }
    
    #「SeekableIterator」を実装する
    class MySeekableIterator extends MyIterator
                                          implements SeekableIterator{
        private $seek_flg = false;
        public function seek($target){
            echo " * seek() *\n";
            if($this->seek_flg || is_null($target))
                return;
            while(list($key) = each($this->arr)){
                if($key === $target){
                    prev($this->arr);
                    $this->seek_flg = true;
                    return;
                }
            }
        }
    }
    
    #「RecursiveIterator」を実装する
    class MyRecursiveIterator extends MyIterator
                                          implements RecursiveIterator{
        public function &current(){
            if(is_array($current = $this->arr[key($this->arr)]))
                return;
            return $current;
        }
        public function hasChildren(){
            echo " * hasChildren() * ";
            return is_array($this->arr[key($this->arr)]);
        }
        public function getChildren(){
            echo " * getChildren() *\n";
            return new MyRecursiveIterator($this->arr[key($this->arr)]);
        }
    }
    
    #「Iterator」処理用関数を定義する
    function iteration(Iterator $iterator){
        echo "------ begin iteration() ------\n";
        foreach($iterator as $key => $val){
            echo "{$key} : {$val}\n";
        }
        echo "------ end iteration() ------\n\n";
    }
    
    #「SeekableIterator」処理用関数を定義する
    function s_iteration(SeekableIterator $s_iterator, $target = null){
        echo "------ begin s_iteration() ------\n";
        foreach($s_iterator as $key => $val){
            $s_iterator->seek($target);
            if($s_iterator->valid())
                echo $s_iterator->key(), " : ", $s_iterator->current(), "\n";
        }
        echo "------ end s_iteration() ------\n\n";
    }
    
    #「RecursiveIterator」処理用関数を定義する
    function r_iteration(RecursiveIterator $r_iterator){
        static $indent = -1;
        echo str_repeat("   ", ++$indent),
             "------ begin r_iteration() ------\n";
        foreach($r_iterator as $key => $val){
            echo str_repeat("   ", $indent), "{$key} : ";
            if($r_iterator->hasChildren()){
                echo "\n", str_repeat("   ", $indent);
                r_iteration($r_iterator->getChildren());
            }
            echo " {$val}\n";
        }
        echo str_repeat("   ", $indent--),
             "------ end r_iteration() ------\n\n";
    }
    
    $arr1 = array(
                   "nda"=>"HODE1",
                   "HODE2",
                   "hode" => "HODE3",
                   "HODE4",
                   "HODE5",
                   "HODE6",
                   "HODE7"
                  );
    $arr2 = array(
                   "HODE1",
                   "hode" => "HODE2",
                   array(
                          "HODE3_1",
                          array(
                                 "HODE3_2_1",
                                 "HODE3_2_2"
                                ),
                          "HODE3_3",
                          array(
                                 array(
                                        "HODE3_4_1_1",
                                        "HODE3_4_1_2",
                                        "HODE3_4_1_3"
                                      )
                               )
                         ),
                   "HODE4"
                  );
    
    iteration(new MyIterator($arr1));
    
    s_iteration(new MySeekableIterator($arr1), "hode");
    s_iteration(new MySeekableIterator($arr1), 3);
    s_iteration(new MySeekableIterator($arr1), 10);
    
    r_iteration(new MyRecursiveIterator($arr1));
    r_iteration(new MyRecursiveIterator($arr2));
?>
</pre>
------ begin iteration() ------
nda : HODE1
0 : HODE2
hode : HODE3
1 : HODE4
2 : HODE5
3 : HODE6
4 : HODE7
------ end iteration() ------

------ begin s_iteration() ------
 * seek() *
hode : HODE3
 * seek() *
1 : HODE4
 * seek() *
2 : HODE5
 * seek() *
3 : HODE6
 * seek() *
4 : HODE7
------ end s_iteration() ------

------ begin s_iteration() ------
 * seek() *
3 : HODE6
 * seek() *
4 : HODE7
------ end s_iteration() ------

------ begin s_iteration() ------
 * seek() *
------ end s_iteration() ------

------ begin r_iteration() ------
nda :  * hasChildren() *  HODE1
0 :  * hasChildren() *  HODE2
hode :  * hasChildren() *  HODE3
1 :  * hasChildren() *  HODE4
2 :  * hasChildren() *  HODE5
3 :  * hasChildren() *  HODE6
4 :  * hasChildren() *  HODE7
------ end r_iteration() ------

------ begin r_iteration() ------
0 :  * hasChildren() *  HODE1
hode :  * hasChildren() *  HODE2
1 :  * hasChildren() * 
 * getChildren() *
   ------ begin r_iteration() ------
   0 :  * hasChildren() *  HODE3_1
   1 :  * hasChildren() * 
    * getChildren() *
      ------ begin r_iteration() ------
      0 :  * hasChildren() *  HODE3_2_1
      1 :  * hasChildren() *  HODE3_2_2
      ------ end r_iteration() ------

 
   2 :  * hasChildren() *  HODE3_3
   3 :  * hasChildren() * 
    * getChildren() *
      ------ begin r_iteration() ------
      0 :  * hasChildren() * 
       * getChildren() *
         ------ begin r_iteration() ------
         0 :  * hasChildren() *  HODE3_4_1_1
         1 :  * hasChildren() *  HODE3_4_1_2
         2 :  * hasChildren() *  HODE3_4_1_3
         ------ end r_iteration() ------

 
      ------ end r_iteration() ------

 
   ------ end r_iteration() ------

 
2 :  * hasChildren() *  HODE4
------ end r_iteration() ------

まず、「Iterator」を実装した「MyIterator」クラスを定義。「SeekableIterator」を実装した「MySeekableIterator」クラスと、「RecursiveIterator」を実装した「MyRecursiveIterator」クラスを定義、何れも「MyIterator」クラスを継承します。

各種「イテレータ」用に、「iteration()」関数、「s_iteration()」関数、「r_iteration()」関数を定義しました。

通常の配列「変数$arr1」と、多次元配列「変数$arr2」を定義し、これを引数に各種「イテレータ」オブジェクトを生成して、更にこれを引数に各種「イテレータ」用関数を実行しました。

s_iteration()」関数内では、「SeekableIterator」が備える「seek()」メソッドを使用していますが、このメソッドは、「指定された位置まで内部配列のポインタを移動する」機能を持っているはずのメソッドです。なお、媒介用変数にセットされている添え字と要素がシーク前の値なので、「key()」メソッドと「current()」メソッドをコールして適切な値を得ます。

r_iteration()」関数内では、「hasChildren()」メソッド(「ポインタが指す位置の要素が配列であるか否かをチェックする」機能を持つ)をコールし、「TRUE」を返した時に「getChildren()」メソッド(「ポインタが指す位置の要素(配列)を引数に「リカーシブイテレータ」オブジェクトを返す」機能を持つ)をコールして、返って来た「リカーシブイテレータ」オブジェクトを引数に「r_iteration()」関数を再帰的にコール・・・を繰り返します。

少々ややこしいですが、ここまでが「イテレータ」の基本です。

定義済みの(実用的な)イテレータ

PHPには予め、幾つかの「イテレータ」が定義されています。通常はこの定義済みの「イテレータ」を使用します。態々自分で「イテレータ」を定義する必要はないのです。

何と。

自分で定義することも出来ますが、定義済みのものを使った方がより安全で、正確で、何より楽です。ちなみに、定義済みの「イテレータ」は「Standard PHP Library(SPL)関数」として定義されています。

「SPL関数」のより詳細な情報は以下のサイトで参照出来ます。

SPL - Standard PHP Library(英語)

と、いうわけで、ここでは定義済みの「イテレータ」にはどのようなものがあるのかだけ、下記のスクリプトを実行して見てみます。

<pre>
<?php
    #有効な「SPLクラス・インターフェイス」を出力する
    
    $arr["implements 'Iterator'"]          = array();
    $arr["implements 'IteratorAggregate'"] = array();
    $arr["ETC"]                            = array();
    
    foreach(spl_classes() as $spl){
        $spl_obj =  new ReflectionClass($spl);
        if($spl_obj->implementsInterface("Iterator"))
            $arr["implements 'Iterator'"][] = $spl;
        else if($spl_obj->implementsInterface("IteratorAggregate"))
            $arr["implements 'IteratorAggregate'"][] = $spl;
        else
            $arr["ETC"][] = $spl;
        
    }
    
    print_r($arr);
    
    
    
    echo "<hr />";
    
    
    
    unset($arr);
    
    #「SPLクラス・インターフェイス」が備えるメソッド名を取得・出力する
    
    $arr["Interface"]         = array();
    $arr["Class"]["abstract"] = array();
    $arr["Class"]["concrete"] = array();
    
    foreach(spl_classes() as $spl){
        $buf_arr = array();
        $spl_obj = new ReflectionClass($spl);
        $spl_methods_arr = $spl_obj->getMethods();
        foreach($spl_methods_arr as $spl_methods_obj)
            $buf_arr[] = $spl_methods_obj->getName();
        if($spl_obj->isInterface())
            $arr["Interface"][$spl] = $buf_arr;
        else{
            if($spl_obj->isAbstract())
                $arr["Class"]["abstract"][$spl] = $buf_arr;
            else
                $arr["Class"]["concrete"][$spl] = $buf_arr;
        }
    }
    
    print_r($arr);
?>
</pre>
Array
(
    [implements 'Iterator'] => Array
        (
            [0] => ArrayIterator
            [1] => CachingIterator
            [2] => CachingRecursiveIterator
            [3] => DirectoryIterator
            [4] => FilterIterator
            [5] => LimitIterator
            [6] => ParentIterator
            [7] => RecursiveDirectoryIterator
            [8] => RecursiveIterator
            [9] => RecursiveIteratorIterator
            [10] => SeekableIterator
            [11] => SimpleXMLIterator
        )

    [implements 'IteratorAggregate'] => Array
        (
            [0] => ArrayObject
        )

    [ETC] => Array
        (
        )

)

Array ( [Interface] => Array ( [RecursiveIterator] => Array ( [0] => hasChildren [1] => getChildren [2] => current [3] => next [4] => key [5] => valid [6] => rewind ) [SeekableIterator] => Array ( [0] => seek [1] => current [2] => next [3] => key [4] => valid [5] => rewind ) ) [Class] => Array ( [abstract] => Array ( [FilterIterator] => Array ( [0] => __construct [1] => rewind [2] => valid [3] => key [4] => current [5] => next [6] => getInnerIterator [7] => accept ) ) [concrete] => Array ( [ArrayObject] => Array ( [0] => __construct [1] => offsetExists [2] => offsetGet [3] => offsetSet [4] => offsetUnset [5] => append [6] => getArrayCopy [7] => count [8] => getIterator ) [ArrayIterator] => Array ( [0] => __construct [1] => offsetExists [2] => offsetGet [3] => offsetSet [4] => offsetUnset [5] => append [6] => getArrayCopy [7] => count [8] => rewind [9] => current [10] => key [11] => next [12] => valid [13] => seek ) …省略 [SimpleXMLIterator] => Array ( [0] => rewind [1] => valid [2] => current [3] => key [4] => next [5] => hasChildren [6] => getChildren [7] => __construct [8] => asXML [9] => xpath [10] => attributes [11] => children ) ) ) )

spl_classes()」関数は、「有効なSPLクラス名・インターフェイス名を配列に格納して返す」関数です。サンプルの結果を見ると、全て「イテレータ」に関係するクラス・インターフェイスである事が判ります。ついでに各SPLクラス・インターフェイスを分類して、更に備えるメソッド名を出力しました。

サンプル中の「ReflectionClass」クラス等については「リフレクション」のページで解説します。

最後に、「定義済みイテレータ」の中の「SimpleXMLIterator」の使い方だけ簡単に解説します。以下のサンプルと結果をご覧下さい。

「iteration_xml.xml」という名前のXMLドキュメントを用意します。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE site [
<!ENTITY title "ほでなすPHP">
<!ENTITY author "しげ">
<!ENTITY php "PHP:Hypertext Preprocessor">
]>
<site>
  <title>&title;へようこそ!</title>
  <author>&author;</author>
  <description>PHP「&php;」入門者向けサイトです。</description>
  <contents>
    <page>
      <name>PHPの基本</name>
      <summary>入門講座です。</summary>
    </page>
    <page>
      <name>関数ミニリファレンス</name>
      <summary>関数のサンプルを交えたミニリファレンスです。 </summary>
    </page>
  </contents>
</site>

このXMLドキュメントを下記のサンプルでロードします。

<pre>
<?php
    $xml = file_get_contents("iteration_xml.xml");
    
    function xml_iteration(SimpleXMLIterator $xml_iterator){
        static $indent = -1;
        echo str_repeat("   ", ++$indent),
             "------ begin xml_iteration() ------\n";
        foreach($xml_iterator as $key => $val){
            echo str_repeat("   ", $indent), "{$key} : ";
            if($xml_iterator->hasChildren()){
                echo "\n", str_repeat("   ", $indent);
                xml_iteration($xml_iterator->getChildren());
            }
            echo trim($val), "\n";
        }
        echo str_repeat("   ", $indent--),
             "------ end xml_iteration() ------\n";
    }
    
    $xml_iterator = new SimpleXMLIterator($xml);
    
    xml_iteration($xml_iterator);
    
    echo "<hr />";
    
    highlight_string($xml_iterator->asXML());
?>
</pre>
------ begin xml_iteration() ------
title : ほでなすPHPへようこそ!
author : しげ
description : PHP「PHP:Hypertext Preprocessor」入門者向けサイトです。
contents : 
   ------ begin xml_iteration() ------
   page : 
         ------ begin xml_iteration() ------
      name : PHPの基本
      summary : 入門講座です。
      ------ end xml_iteration() ------

   page : 
         ------ begin xml_iteration() ------
      name : 関数ミニリファレンス
      summary : 関数のサンプルを交えたミニリファレンスです。
      ------ end xml_iteration() ------

   ------ end xml_iteration() ------

------ end xml_iteration() ------

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE site [ <!ENTITY title "ほでなすPHP"> <!ENTITY author "しげ"> <!ENTITY php "PHP:Hypertext Preprocessor"> ]> <site> <title>&title;へようこそ!</title> <author>&author;</author> <description>PHP「&php;」入門者向けサイトです。</description> <contents> <page> <name>PHPの基本</name> <summary>入門講座です。</summary> </page> <page> <name>関数ミニリファレンス</name> <summary>関数のサンプルを交えたミニリファレンスです。 </summary> </page> </contents> </site>

使い方あってるか微妙ですが、こんな感じで。

作成日:2005年01月22日 最終更新日:2005年01月22日
【通常モード で表示】