PHP interfaces IteratorAggregate vs Iterator? PHP interfaces IteratorAggregate vs Iterator? arrays arrays

PHP interfaces IteratorAggregate vs Iterator?


I assume IteratorAggregate is for cases where you want to save time, and Iterator is for cases where you need fine control over iteration through your object. For example it lets you add custom exceptions on next(), key() or prev() failures, caching(if you iterate through something that takes data over the network), preprocess values before returning them.


IteratorAggregate is an easy way to implement an Iterator. The disadvantage is you cannot add next(), key(), etc methods, as they won't get called during a normal foreach traversal.

If you need to implement custom methods, you need to implement an OuterIterator or (easier) to extend an IteratorIterator.

The advantage with IteratorAggregate is the speed, it is much more faster than its alternatives. The problem with it it's that... despite the name it's not an iterator but a traversable (so again, no next, key, current, valid, rewind methods).

Here's a simple benchmark I run on my poor laptop with 1 million of iterations for each one of IteratorAggregate, IteratorIterator and OuterIteratorboth using iterator_to_array and foreach:(also note the echo inside Aggregate's next method: no 'x' gets printed, proof it is never invoked)

$ repeat 3 php benchIterator.php------------------------------------Outer_ToArray: 569.61703300476 msAggregate_ToArray: 552.38103866577 msIteratorIt_ToArray: 546.95200920105 msOuter_Foreach: 1679.8989772797 msIteratorIt_Foreach: 1019.6850299835 msAggregate_Foreach: 367.35391616821 ms------------------------------------Outer_ToArray: 570.75309753418 msAggregate_ToArray: 544.40784454346 msIteratorIt_ToArray: 555.06300926208 msOuter_Foreach: 1682.2130680084 msIteratorIt_Foreach: 988.21592330933 msAggregate_Foreach: 356.41598701477 ms------------------------------------Outer_ToArray: 566.06101989746 msAggregate_ToArray: 543.1981086731 msIteratorIt_ToArray: 546.28610610962 msOuter_Foreach: 1663.2289886475 msIteratorIt_Foreach: 995.28503417969 msAggregate_Foreach: 356.16087913513 ms

Here's the code I used for the benchmark:

<?phpclass Aggregate implements \IteratorAggregate{    protected $var;    public function __construct($var = null)    {        if (is_array($var)) {            $this->var = new ArrayIterator($var);        }        if ($var instanceof \Traversable) {            $this->var = $var;        }    }    public function next()    {        echo 'x';    }    public function toArray()    {        return iterator_to_array($this->var, true);    }    public function getIterator()    {        return $this->var;    }}class Outer implements \OuterIterator{    protected $var;    public function __construct($var = null)    {        if (is_array($var)) {            $this->var = new ArrayIterator($var);        }        if ($var instanceof \Traversable) {            $this->var = $var;        }    }    public function toArray()    {        return iterator_to_array($this->var, true);    }    public function getInnerIterator()    {        return $this->var;    }    public function current()    {        return $this->var->current();    }    public function next()    {        $this->var->next();    }    public function key()    {        return  $this->var->key();    }    public function valid()    {        return  $this->var->valid();    }    public function rewind()    {     $this->var->rewind();    }}class IteratorIt extends IteratorIterator{    public function __construct($var = null)    {        if (is_array($var)) {            $var = new ArrayIterator($var);        }        parent::__construct($var);    }    public function toArray()    {        return iterator_to_array($this->getInnerIterator(), true);    }    public function getIterator()    {        return $this->getInnerIterator();    }}function bench($name, $test){    echo "$name: ";    $start = microtime(true);    $test();    $time = microtime(true);    $time -= $start;    echo ($time * 1000) . ' ms' . PHP_EOL;}$max = 1e6;$array = range (1, 1e6);$testSuites = [    'Outer_ToArray' => function () use ($max, $array) {        $iterator = new Outer($array);        $r = $iterator->toArray();    },    'Aggregate_ToArray' => function () use ($max, $array) {        $iterator = new Aggregate($array);        $r = $iterator->toArray();    },    'IteratorIt_ToArray' => function () use ($max, $array) {        $iterator = new IteratorIt($array);        $r = $iterator->toArray();    },    'Outer_Foreach' => function () use ($max, $array) {        $iterator = new Outer($array);        foreach ($iterator as $k => $v)        {        }    },    'IteratorIt_Foreach' => function () use ($max, $array) {        $iterator = new IteratorIt($array);        foreach ($iterator as $k => $v)        {        }    },    'Aggregate_Foreach' => function () use ($max, $array) {        $iterator = new Aggregate($array);        foreach ($iterator as $k => $v)        {        }    },];echo '------------------------------------'.PHP_EOL;foreach ($testSuites as $name => $test) {    bench($name, $test);}


By implementing IteratorAggregate, we delegate the job of implementing iterator functions( key(), next(),current(), valid(), rewind()) to other class (by implementing only getIterator().

This way, it helps us achieve separate of concerns in OOP.