Search code examples
phpiteratorgeneratoriterable

PHP - Iterating twice a generic iterable


In PHP 7.1 there is a new iterable psudo-type which abstracts arrays and Traversable objects.

Suppose that in my code I have a class like the following:

class Foo
{
    private $iterable;

    public function __construct(iterable $iterable)
    {
        $this->iterable = $iterable;
    }

    public function firstMethod()
    {
        foreach ($this->iterable as $item) {...}
    }

    public function secondMethod()
    {
        foreach ($this->iterable as $item) {...}
    }
}

This works fine is $iterable is an array or an Iterator, except when $iterable is a Generator. In that case in fact, calling firstMethod() and then secondMethod() would produce the following Exception: Cannot traverse an already closed generator.

Is there a way to avoid this issue?


Solution

  • Generators can't be rewound. If you want to avoid this issue, you have to make a new generator. This can be done automatically if you create an object that implements IteratorAggregate:

    class Iter implements IteratorAggregate
    {
        public function getIterator()
        {
            foreach ([1, 2, 3, 4, 5] as $i) {
                yield $i;
            }
        }
    }
    

    Then just pass an instance of this object as your iterator:

    $iter = new Iter();
    $foo = new Foo($iter);
    $foo->firstMethod();
    $foo->secondMethod();
    

    Output:

    1
    2
    3
    4
    5
    1
    2
    3
    4
    5