PHP SeekableTerator:捕获边界异常还是检查有效()方法?

PHP SeekableTerator:捕获边界异常还是检查有效()方法?,php,exception-handling,spl,seekableiterator,Php,Exception Handling,Spl,Seekableiterator,所以我不确定这是否是PHP的错误设计,或者是否有一个可以理解的逻辑来处理同一接口的不一致结果 SeekableIterator接口有两种方法(seek和valid),它们要么相互冲突,要么应该一致地工作,但我看到了这两种方法 接口的文档说明seek应该抛出类OutOfBoundsException的异常,但这似乎否定了valid的有用性,除非在抛出异常(显然必须捕获)之前更新迭代器位置(使valid返回false) 三个测试示例 例1。 实现SeekableIterator的自定义类,如文档中的

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

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

接口的文档说明
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。测试: 测试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

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

这里的
directoryIterator::seek()
方法似乎没有实现异常。相反,它将不返回任何值,并让
valid()
处理它

您的另一个示例是
ArrayObject::seek()
确实“正确”工作,并抛出
边界外异常

推理很简单:
ArrayObject
(很可能也是大多数自定义实现)会事先知道它包含多少元素,因此可以快速检查其边界。然而,
DirectoryIterator
必须逐个从磁盘读取目录实体,才能到达给定位置。它通过在循环中直接调用
valid()
next()
来实现。这就是为什么
键()
已更改,并且
valid()
返回
0
的原因

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

旁注:如果您想向后搜索DirectoryIterator中的位置,它将首先重置迭代器,然后再次开始迭代每个元素。因此,如果您位于位置1000,并执行
$it->seek(999)
,它实际上会再次迭代999个元素

总之,
directoryinterator
不是
seekableIterator
接口的良好实现。它旨在快速跳转到迭代器中的某个元素,显然,对于directoryIterator,这是不可行的。相反,必须进行完整的迭代,这将导致迭代器状态发生变化

seekableIterator
接口对于使用迭代器范围执行某些操作的Filtererator非常有用。在SPL中,这只是
LimitIterator
。当您这样做时:

$it = new ArrayIterator(range('a','z'));
$it = new LimitIterator($it, 5, 10));
当limitIterator检测到给定的迭代器已经实现了
SeekableTerator
接口时,它将调用
seek()
快速跳转到第5个元素,否则它将只进行迭代,直到到达第5个元素

结论:当您无法快速跳转到某个位置或检查边界时,不要使用
seekableIterator
。在最好的情况下,您什么也得不到,在最坏的情况下,您得到的迭代器会在不知道原因的情况下更改状态


要回答您的问题:
seek()
应该引发异常,而不是更改状态。
directoryIterator
(可能还有其他一些)应该更改为不实现
seekableIterator
,或者通过查找
seek()
(但这并不能解决向后查找时的“倒带”问题)。

非常好,谢谢。就我个人而言,我认为抛出异常不应该成为常态,但您关于directoryIterator为什么是SeekableTerator的一个糟糕用例的解释是一个有力的反证,并指出了为什么应该将其作为一个特殊异常处理,而不是重新配置接口,使其不承担为接口抛出的异常。更具体地说,我认为,为特定接口抛出异常不应该成为常态,因为它会增加关于何时依赖
valid
方法与何时假设异常的混淆。通过抛出异常并更新位置来改变迭代器的状态是否会更混乱/更具破坏性,从而使
valid
返回false?不。我认为这是可能的,但是,我不知道这是否会被视为BC中断,并会在userland代码中引起任何问题(尽管如此,该代码可能太脆弱而无法开始)。也许这是对PHP7的一个很好的修复。@Anthony这实际上是设计的行为,正如a所述。但是,我认为应该有一个异常。@Anthony这是!在PHP5.6.15及更高版本中,
DirectoryIterator
将抛出一个异常,就像其他
SeekableTerator
实现一样。
$it = new ArrayIterator(range('a','z'));
$it = new LimitIterator($it, 5, 10));