Поэтому я не уверен, является ли это ошибочным дизайном с PHP или есть понятная логика для обработки противоречивых результатов для одного и того же интерфейса.
Интерфейс SeekableIterator имеет два метода (seek
а также valid
) которые либо находятся в конфликте друг с другом, либо должны работать последовательно друг с другом, но я вижу и то, и другое.
В документации по интерфейсу сказано, что seek
должен выдать исключение класса OutOfBoundsException, но это, кажется, сводит на нет полезность valid
если позиция итератора не обновляется valid
верните false), прежде чем выдать исключение (которое, очевидно, должно быть перехвачено).
Класс:
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]);
}
}
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 Код:
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 Код:
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()
возвращает истину?
Кажется, что directoryIterator::seek()
Метод здесь не реализован за исключением. Вместо этого он просто не вернет значение и позволит valid()
справиться.
Ваш другой пример, ArrayObject::seek()
работает «правильно» и бросает OutOfBoundsException
,
Аргументация проста: ArrayObject
(и, скорее всего, большинство пользовательских реализаций также) будут заранее знать, сколько элементов в нем содержится, и, таким образом, смогут быстро проверить его границы. DirectoryIterator
однако, чтобы получить заданную позицию, необходимо прочитать объекты каталога с диска по одному. Это буквально вызывает valid()
а также next()
в петле. Это причина, почему key()
изменился, и valid()
возвращается 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()
(но это не исправляет «перемотку» при поиске обратной задачи).
Других решений пока нет …