AppendIterator适配空Generator报错:无法遍历已关闭生成器,如何解决?
问题解答
这是PHP标准库的Bug吗?
不是。这是AppendIterator的设计行为与PHP生成器(Generator)的特性冲突导致的正常现象:
- PHP生成器是单向迭代器,一旦遍历结束(哪怕没有产出任何元素),生成器就会进入关闭状态,无法通过
rewind()方法重置迭代位置。 AppendIterator在遍历前会对每个子迭代器调用一次rewind(),实际遍历过程中还会再次操作子迭代器。当子迭代器是已关闭的生成器时,就会触发Cannot traverse an already closed generator错误。
继续使用生成器的解决方案
方案1:用IteratorAggregate包装生成器
通过IteratorAggregate接口,让每次迭代都生成一个全新的Generator实例,避免重复使用已关闭的生成器。修改Fruits类的getApples方法:
class Fruits { public function __construct(protected array $items) {} public function getApples(): \IteratorAggregate { return new class($this->items) implements \IteratorAggregate { public function __construct(private array $items) {} public function getIterator(): \Iterator { foreach ($this->items as $item) { if ($item === 'apple') { yield $item; } } } }; } }
当AppendIterator需要迭代子元素时,会调用getIterator()方法,每次都会得到一个新的生成器,自然不会出现已关闭的问题。
方案2:将生成器转换为可重复迭代的容器
如果生成器的数据量不大,可以先将其转换为ArrayIterator,这类迭代器支持重复rewind()操作:
public function test_iterator() { $fruits = new Fruits(['banana', 'banana', 'orange']); $concat = new AppendIterator(); // 先将生成器转换为ArrayIterator $applesArray = iterator_to_array($fruits->getApples()); $concat->append(new ArrayIterator($applesArray)); $array = iterator_to_array($concat); $this->assertEquals(0, count($array)); }
注意:此方法会将生成器的所有元素一次性加载到内存中,不适合处理大数据量的生成器。
内容的提问来源于stack exchange,提问作者gvlasov
相关产品推荐
相关产品推荐

