AppendIterator 不能很好地处理空生成器,如何解决它?

问题描述 投票:0回答:1

有时,当我必须将多个迭代器连接成一个迭代器时,它只是一个空迭代器。我使用

AppendIterator
来连接迭代器。当我将一个空的
Generator
yield
ing 方法添加到
AppendIterator
时,我希望它成为一个具有 0 个元素的迭代器:

<?php

namespace Tests\cases\domain;

use AppendIterator;
use PHPUnit\Framework\TestCase;

class IteratorTest extends TestCase
{

    public function test_iterator() {
        $fruits = new Fruits(['banana', 'banana', 'orange']);
        $concat = new AppendIterator();
        $concat->append($fruits->getApples());
        $array = iterator_to_array($concat);
        $this->assertEquals(0, count($array));
    }

}

class Fruits
{

    public function __construct(protected array $items) {

    }

    public function getApples(): \Iterator {
        foreach ($this->items as $item) {
            if ($item === 'apple') {
                yield $item;
            }
        }
    }
}

但是,当遍历这样的迭代器时,会产生错误:

PHPUnit 11.3.6 by Sebastian Bergmann and contributors.

Runtime:       PHP 8.3.12
Configuration: /home/me/Projects/personal/myproject/framework/phpunit.xml


Exception: Cannot traverse an already closed generator
/home/me/Projects/personal/myproject/tests/cases/domain/IteratorTest.php:15

Time: 00:00.002, Memory: 10.00 MB

There was 1 error:

1) Tests\cases\domain\IteratorTest::test_iterator
Exception: Cannot traverse an already closed generator

/home/me/Projects/personal/myproject/tests/cases/domain/IteratorTest.php:15

ERRORS!
Tests: 1, Assertions: 0, Errors: 1.
Process finished with exit code 2

它似乎试图遍历生成器两次,这是不可接受的,因为生成器不会自动倒带。

我该如何解决这个问题?我没发现我的发电机有什么问题。一切都指向

AppendIterator
实现中的可疑之处,这是 PHP 标准库中的错误吗?我可以在代码中仍然使用生成器的同时解决这个问题吗?

php iterator generator
1个回答
0
投票

我理解你的问题!出现“无法遍历已关闭的生成器”异常是因为 PHP 中的生成器是单遍的,这意味着它们不能被遍历多次。 AppendIterator 正在尝试遍历已经关闭的生成器。这是一个继续使用发电机同时避免发电机关闭问题的解决方案:

我们可以将生成器逻辑封装在一个单独的类中,并确保遍历逻辑得到正确处理:

<?php

namespace Tests\cases\domain;

use AppendIterator;
use IteratorAggregate;
use ArrayIterator;
use PHPUnit\Framework\TestCase;

class IteratorTest extends TestCase
{
    public function test_iterator() {
        $fruits = new Fruits(['banana', 'banana', 'orange']);
        $concat = new AppendIterator();
        $concat->append($fruits->getApples());
        $array = iterator_to_array($concat);
        $this->assertEquals(0, count($array));
    }
}

class Fruits implements IteratorAggregate
{
    protected array $items;

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

    public function getApples(): \Iterator {
        return new ArrayIterator(array_filter($this->items, function ($item) {
            return $item === 'apple';
        }));
    }

    public function getIterator(): \Iterator {
        return $this->getApples();
    }
}

在此解决方案中:

我使用 IteratorAggregate 和 ArrayIterator 来处理生成器逻辑。

getApples 方法返回一个经过过滤的 ArrayIterator 而不是生成器。

ArrayIterator 是一个可以多次遍历而不需要关闭的迭代器。

通过此修改,您应该避免“无法遍历已关闭的生成器”错误并保持代码按预期工作。

希望这对您有帮助! 🚀

© www.soinside.com 2019 - 2024. All rights reserved.