28

The IteratorAggregate is an interface to create an external Iterator:

class myData implements IteratorAggregate
{
    public $property1 = "Public property one";
    public $property2 = "Public property two";
    public $property3 = "Public property three";

    public function __construct()
    {
        $this->property4 = "last property";
    }

    public function getIterator()
    {
        return new ArrayIterator($this);
    }
}

$obj = new myData;

And you'll be able to traverse the object using foreach:

foreach($obj as $key => $value) {
    var_dump($key, $value);
    echo "\n";
}

While Iterator is an interface for external iterators or objects that can be iterated themselves internally:

class myIterator implements Iterator
{
    private $position = 0;
    private $array = array('one', 'two', 'three');

    function rewind()
    {
        $this->position = 0;
    }

    function current()
    {
        return $this->array[$this->position];
    }

    function key()
    {
        return $this->position;
    }

    function next()
    {
        ++$this->position;
    }

    function valid()
    {
        return isset($this->array[$this->position]);
    }
}

And again, you can traverse it basically the same way:

$it = new myIterator;

foreach($it as $key => $value) {
    var_dump($key, $value);
    echo "\n";
}

So can anyone explain why we need two interfaces and what's the difference between them?

7 Answers 7

25

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.

Sign up to request clarification or add additional context in comments.

1 Comment

And if you want to save even more time, you can simply yield the values from getIterator.
15

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 ms
Aggregate_ToArray: 552.38103866577 ms
IteratorIt_ToArray: 546.95200920105 ms
Outer_Foreach: 1679.8989772797 ms
IteratorIt_Foreach: 1019.6850299835 ms
Aggregate_Foreach: 367.35391616821 ms
------------------------------------
Outer_ToArray: 570.75309753418 ms
Aggregate_ToArray: 544.40784454346 ms
IteratorIt_ToArray: 555.06300926208 ms
Outer_Foreach: 1682.2130680084 ms
IteratorIt_Foreach: 988.21592330933 ms
Aggregate_Foreach: 356.41598701477 ms
------------------------------------
Outer_ToArray: 566.06101989746 ms
Aggregate_ToArray: 543.1981086731 ms
IteratorIt_ToArray: 546.28610610962 ms
Outer_Foreach: 1663.2289886475 ms
IteratorIt_Foreach: 995.28503417969 ms
Aggregate_Foreach: 356.16087913513 ms

Here's the code I used for the benchmark:

<?php

class 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);
}

Comments

7

What's the difference between them?

The difference in names("Aggregate").

What does Aggregate mean?

Aggregate state guarantees the consistency of changes being made within the aggregate by forbidding external objects from holding references to its members. (wikipedia)


The TC code:

class myData implements IteratorAggregate {//your implementation}

class myIterator implements Iterator {//your implementation}

Here's the code for clarity of Aggregate:

$renderFunction = function($iterator) {
    foreach($iterator as $key => $value) {
        echo "$key: $value\n";
        foreach($iterator as $key => $value) {
           echo "    $key: $value\n";
        }
    }
};

$renderFunction(new myData); // Aggregate on
$renderFunction(new myIterator); // Aggregate off

What do you expect in cases?

https://3v4l.org/4tECJ

property1: Public property one
    property1: Public property one
    property2: Public property two
    property3: Public property three
    property4: last property
property2: Public property two
    property1: Public property one
    property2: Public property two
    property3: Public property three
    property4: last property
property3: Public property three
    property1: Public property one
    property2: Public property two
    property3: Public property three
    property4: last property
property4: last property
    property1: Public property one
    property2: Public property two
    property3: Public property three
    property4: last property

vs

0: one
    0: one
    1: two
    2: three

5 Comments

I am curious, in the link you provide, it looks like php7 is slower than php 5.6 for example. Is this normal? or I don't understand the data at all
@Dazag You are right, but it's little diff by time and I don't know is there used opcache....
Personally like this answer the most.
One, two, three... Outputs are not easy to understand. Imho not a useful answer, since you take more time understanding it than answering the OP question
@yolenoyer This code was taken from the original question. It's really hard to understand when two objects contain different data. Perhaps this example 3v4l.org/FXcFv will be easier if you immediately want to see the differences for the aggregate.
6

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.

Comments

4

Primarily it all comes down as to what you're trying to develop, and whether others will be using your classes to develop something.

Simple forms of Iterator work just as well as IteratorAggregate within single level control loops, and are mostly interchangeable.

foreach ($it as $key => $value) {
    // Both examples from the original question will out put the same results.
}

Where Iterator fails is advanced logic, such as when there are multiple nested loops. Simple iterators with only a single index will only end up looping once for all but the deepest control loops, unless you reset it manually within each loop.

$i = $j = 0;
foreach ($it as $key => $value) {
    foreach ($it as $key2 => $value2) {
        // Internal index is reset and then incremented as expected.
        $j++;
    }
    // Loop ends after 1 iteration unless you reset internal position.
    # $it->mySetIteratorPosition($i);
    $i++;
}
$i === 1;
$j === count($it);

The iterator does not have a function to tell it when the loop is broken, so if someone breaks out of one of the nested loops before it's finished, it will readily cause an infinite loop.

foreach ($it as $key => $value) {
    foreach ($it as $key2 => $value2) {
        // Internal pointer is reset back to 0
        break;
    }
    // Internal pointer is incremented from 0 to 1
}
// Unreachable statement

Where IteratorAggregate works, is it creates a new Iterator object whenever a new loop starts. This means that nested loops are easier to maintain and the new Iterator object is discarded when the loop ends (or soon after) by the garbage collector.

If you want to control the processing of rewind, current, next, key or valid manually, then all you will need to do is use a custom Iterator class in getIterator, or alternatively extend OuterIterator.

class myData implements IteratorAggregate
{
    public function getIterator()
    {
        $it = new myIterator($this);
        $it->mySetArray((array) $this);
        return $it;
    }
}

1 Comment

This is a very important difference!
3

So can anyone explain why we need two interfaces and what's the difference between them?

There exists an abstract base interface Traversable in PHP.

If a class implements Traversable you can call it in a foreach loop and iterate over it. However, a class cannot directly implement Traversable since it is a abstract base interface.

Instead, a class can either implement IteratorAggregate or Iterator. So they both have in common, that if you want to make an object callable with a foreach loop it needs to implement one of these (or any child of them).

One implements Iterator if one wants to control the pointer and to give precise instruction how it should iterate. If one wants to use an existing Iterator that is used in the foreach loop one implements IteratorAggregate.

And you can always convert an IteratorAggregate back to an Iterator with a IteratorIterator. There is also a nice example at the PHP docs

Comments

0

Iterator interfaces for me make more readable code. If you need more specific methods for your business logic, then you can implement than do what you want. Or create quickly empty class or instance directly. Main reason is clean code.

4 Comments

Can you explain what you mean by "readable code"? That's not what interfaces are for
I mean code is more readable examples are on last message with short named methods from interfaces, not with with native functions.
Can you clarify that once more? I don't get that difference between "short named methods" and "native functions"
Difference is OOP and more safely and optimized.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.