PHP SeekableIterator:捕获 OutOfBoundsException 或检查 valid() 方法?

PHP SeekableIterator : catch OutOfBoundsException or check valid() method?

所以我不确定这是否是 PHP 的错误设计,或者是否存在处理同一界面的不一致结果的可理解逻辑。

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

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

三个测试例子

示例 1.

自定义 class 实现 SeekableIterator,如文档中的示例所提供:

class:

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() return 是真的吗?

这里的directoryIterator::seek()方法好像没有异常实现。相反,它只是 return 没有价值,让 valid() 处理它。

你的另一个例子,ArrayObject::seek() 确实有效 "correctly" 并抛出一个 OutOfBoundsException.

原因很简单:ArrayObject(很可能还有大多数自定义实现)会预先知道它包含多少元素,因此可以快速检查它的边界。但是,DirectoryIterator 必须从磁盘中一个一个地读取目录实体才能到达给定位置。它通过在循环中逐字调用 valid()next() 来实现。这就是为什么key()变了,valid()return变0.

的原因

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

旁注:如果您想在 DirectoryIterator 中向后查找位置,它将首先重置迭代器,然后再次开始迭代每个元素。因此,如果您在位置 1000 上执行 $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() 之前有多少条目(但这并不能解决 '倒带问题。