4

所以我不确定这是否是 PHP 的错误设计,或者是否有一个可以理解的逻辑来处理同一接口的不一致结果。

SeekableIterator 接口有两种方法 (seekvalid),它们或者相互冲突,或者应该相互一致地工作,但我看到了两者。

该接口的文档说seek应该抛出类 OutOfBoundsException 的异常,但这似乎否定了valid除非迭代器位置valid在抛出异常(显然必须被捕获)之前更新(使返回 false)的有用性。

三个测试示例

示例 1。

实现 SeekableIterator 的自定义类,由文档中的示例提供:

班上:

class MySeekableIterator implements SeekableIterator {

    private $position;

    private $array = array(
        "first element",
        "second element",
        "third element",
        "fourth element"
    );

    /* Method required for SeekableIterator interface */

    public function seek($position) {
        if (!isset($this->array[$position])) {
            throw new OutOfBoundsException("invalid seek position ($position)");
        }

        $this->position = $position;
    }

    /* Methods required for Iterator interface */

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

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

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

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

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

示例 1. 测试:

echo PHP_EOL . "Custom Seekable Iterator seek Test" . PHP_EOL;

$it = new MySeekableIterator;

$it->seek(1);
try {
    $it->seek(10);
    echo $it->key() . PHP_EOL;
    echo "Is valid? " . (int) $it->valid() . PHP_EOL;
} catch (OutOfBoundsException $e) {
    echo $e->getMessage() . PHP_EOL;
    echo $it->key() . PHP_EOL; // outputs previous position (1)
    echo "Is valid? " . (int) $it->valid() . PHP_EOL;
}

测试 1 输出:

Custom Seekable Iterator seek Test
invalid seek position (10)
1
Is valid? 1

示例 2:

使用本机 ArrayIterator::seek

测试 2 代码:

echo PHP_EOL . "Array Object Iterator seek Test" . PHP_EOL;

$array = array('1' => 'one',
               '2' => 'two',
               '3' => 'three');

$arrayobject = new ArrayObject($array);
$iterator = $arrayobject->getIterator();

$iterator->seek(1);
try {
    $iterator->seek(5);
    echo $iterator->key() . PHP_EOL;
    echo "Is valid? " . (int) $iterator->valid() . PHP_EOL;
} catch (OutOfBoundsException $e) {
    echo $e->getMessage() . PHP_EOL;
    echo $iterator->key() . PHP_EOL;  // outputs previous position (1)
    echo "Is valid? " . (int) $iterator->valid() . PHP_EOL;
}

测试 2 输出:

Array Object Iterator seek Test
Seek position 5 is out of range
1
Is valid? 1

示例 3:

使用本机 DirectoryIterator::seek

测试 3 代码:

echo PHP_EOL . "Directory Iterator seek Test" . PHP_EOL;

$dir_iterator = new DirectoryIterator(dirname(__FILE__));
$dir_iterator->seek(1);
try {
    $dir_iterator->seek(500);  // arbitrarily high seek position
    echo $dir_iterator->key() . PHP_EOL;
    echo "Is valid? " . (int) $dir_iterator->valid() . PHP_EOL;
} catch (OutOfBoundsException $e) {
    echo $e->getMessage() . PHP_EOL;
    echo $dir_iterator->key() . PHP_EOL;
    echo "Is valid? " . (int) $dir_iterator->valid() . PHP_EOL;
}

测试 3 输出:

Directory Iterator seek Test
90
Is valid? 0

那么,如何合理地期望知道是否在之后使用valid()来确认有效位置,seek($position)同时还预期seek()可能会抛出异常而不是更新位置,从而valid()返回 true?

4

1 回答 1

4

好像directoryIterator::seek()这里的方法没有实现异常。相反,它不会返回任何值,让我们valid()处理它。

您的另一个示例,ArrayObject::seek()确实“正确”工作并抛出OutOfBoundsException.

推理很简单:(ArrayObject很可能也是大多数自定义实现)将事先知道它包含多少元素,因此可以快速检查它的边界。但是DirectoryIterator,必须从磁盘一个一个地读取目录实体才能到达给定的位置。它通过逐字调用valid()next()循环来实现。这就是key()改变并valid()返回的原因0

其他迭代器甚至不会触及当前的迭代器状态,并且可以快速确定您的请求是否在其范围内。

附带说明:如果您想在 DirectoryIterator 中向后查找位置,它将首先重置迭代器,然后再次开始迭代每个元素。因此,如果您在位置 1000 并执行 a $it->seek(999),它实际上会再次迭代 999 个元素。

恕我直言,这DirectoryIterator不是seekableIterator接口的一个很好的实现。它旨在快速跳转到迭代器中的某个元素,显然,使用 directoryIterator 这不是可行的。相反,必须完成一次完整的迭代,这会导致迭代器状态发生变化。

seekableIterator接口对于在迭代器范围内执行某些操作的 filterIterators 很有用。在 SPL 中,这只是LimitIterator. 当你这样做时:

$it = new ArrayIterator(range('a','z'));
$it = new LimitIterator($it, 5, 10));

当limitIterator检测到给定的迭代器实现了seekableIterator接口时,会调用seek()快速跳转到第5个元素,否则只会一直迭代到第5个元素。

结论:seekableIterator当你不能快速跳转到某个位置或检查边界时,不要使用。充其量你什么也得不到,最坏的情况你得到的迭代器会在不知道为什么的情况下改变状态。

回答你的问题:seek()应该抛出异常而不是改变状态。(directoryIterator也许还有其他一些)应该更改为不实现seekableIterator,或者通过找出之前有多少条目seek()(但这并不能解决“倒带”问题时寻求向后问题)。

于 2015-05-30T18:32:00.013 回答