PHP SeekableIterator:捕获 OutOfBoundsException 或检查 valid() 方法?
PHP SeekableIterator : catch OutOfBoundsException or check valid() method?
所以我不确定这是否是 PHP 的错误设计,或者是否存在处理同一界面的不一致结果的可理解逻辑。
SeekableIterator 接口有两个方法(seek
和 valid
),它们要么相互冲突,要么应该一致地工作,但我看到了这两种方法。
接口的文档说 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()
之前有多少条目(但这并不能解决 '倒带问题。
所以我不确定这是否是 PHP 的错误设计,或者是否存在处理同一界面的不一致结果的可理解逻辑。
SeekableIterator 接口有两个方法(seek
和 valid
),它们要么相互冲突,要么应该一致地工作,但我看到了这两种方法。
接口的文档说 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()
之前有多少条目(但这并不能解决 '倒带问题。