Iteratorをimplementsする奴は情弱。IteratorAggregateを使え

という話をずいぶん前にFabienさんがしていたのをふと思い出したので、たまには啓蒙っぽい記事を書いてみる。

[Iterator or IteratorAggregate? - Fabien Potencier](http://fabien.potencier.org/article/45/iterator-or-iteratoraggregate)

## foreach可能なオブジェクトを作る

PHPのオブジェクトは特に何もしなくてもforeachでぐるぐる回して中身を得ることができます。
ただし、得られるものはpublicメンバに限ります。

```php
<?php
class A {
  public $hoge = 1;
  public $fuga = 2;
  private $pri = 3;
  protected $pro = 4;
}

$a = new A;

foreach ($a as $key => $val) {
  var_dump($key, $val);
}
```

```
string(4) "hoge"
int(1)
string(4) "fuga"
int(2)
```

ただ、カプセル化を真面目に行っている皆様方におかれましては、publicなんて使うことはないでしょう。(!)
privateやprotectedなメンバを含むクラスが通常で、そういった複雑なクラスをforeachでうまいこと回せるようにしたいなら、工夫が必要になります。

## Iteratorって面倒くさいよねって話

[Iterator](http://jp2.php.net/manual/ja/class.iterator.php)というPHPの組み込みインターフェースを実装しておくと、オブジェクトをforeachループに突っ込んだ際の挙動を細かく制御できるようになります。
しかし、このIteratorは5つもメソッドを実装せねばならず、面倒くさいです。

```php
interface Iterator extends Traversable
{
  function current();
  function key();
  function next();
  function rewind();
  function valid();
}
```

```php
//冒頭のFabienさんの記事から丸ごと引用
class Foo implements Iterator
{
    protected $attributes = array();
 
    function __construct(array $arr)
    {
        $this->attributes = $arr;
    }

    function rewind()
    {
        reset($this->attributes);
    }
 
    function current()
    {
        return current($this->attributes);
    }
 
    function key()
    {
        return key($this->attributes);
    }
 
    function next()
    {
        return next($this->attributes);
    }
 
    function valid()
    {
        return false !== current($this->attributes);
    }
}
```

うえー。。だいたい決まりきった書き方とはいえ、これ全部にユニットテスト書くの？ 書きたくねーだろ！

## IteratorAggregateならこんなに簡単

[IteratorAggregate](http://jp2.php.net/manual/ja/class.iteratoraggregate.php)もPHPの組み込みインターフェースであり、実装するとforeachで回せるようになります。
そういう意味ではIteratorと似ているんですが、こちらはIteratorオブジェクトを作るところだけ行い、クラス自身では反復の管理を行いません。

なので実装するべきはgetIteratorメソッドただ一つでOK。getIteratorメソッドの中で、新しくIteratorクラスをnewして、返せばいいだけです。

```php
interface IteratorAggregate extends Traversable
{
  function getIterator();
}
```

```php
class Foo implements IteratorAggregate
{
    protected $attributes = array();

    function __construct(array $arr)
    {
        $this->attributes = $arr;
    }

    function getIterator()
    {
        return new ArrayIterator($this->attributes);
    }
}
```

これだけでFooはforeachで回してattributesの中身を取り出せるようになります。

ちなみにIteratorAggregateはPHP5.5から導入されたジェネレータと大変相性がいいです。getIteratorメソッドそのものをジェネレータ関数にしてもよいのです。

```php
class Foo implements IteratorAggregate
{
    protected $attributes = array();

    function __construct(array $arr)
    {
        $this->attributes = $arr;
    }

    function getIterator()
    {
        foreach ($this->attributes as $key => $val) {
            yield $key => $val;
        }
    }
}
```

複雑なループを行いたい場合はジェネレータの方が書きやすいでしょう。
SPLには[結構いろんなIteratorが用意されている](http://jp2.php.net/manual/ja/spl.iterators.php)ので、こいつらを活用すれば自前でIteratorを実装しなければならないケースはだいぶ減ると思います。


## 入れ子foreachへの対応

ちなみに、Iteratorはそれ自体が反復の状態管理を行いますので、同時に複数のループを扱うことができません。具体的に言えば入れ子にすることができません。

IteratorAggregateの場合は、foreachに入るたびに新しいIteratorを返すので、入れ子に対応できます。
この点からも、IteratorAggregateを使ったほうがいいです。

参考： [イテレータを介して見るPHPクラスの内部構造 - hnwの日記](http://d.hatena.ne.jp/hnw/20131025)


## 速度的な話

自前実装したIteratorは、反復のたびにメソッドを何度も呼ぶはめになるため、遅いです。
ArrayIteratorなど、組み込みのIteratorを流用した方が一般的には高速に動作します。


## まとめ

別に、XMLReaderとかストリームといった、低レベルの概念をIterator化するのは問題ないのですが、Webアプリとかで登場するような、ドメインレベルのクラスにIteratorをちまちま実装する行為は普通必要ないと思います。

というわけでIteratorAggregateを使いましょう！