Ниже приведены коды из PHP документа о Итератор с добавлением нескольких строк, чтобы показать позицию.
Как вы можете видеть, объект имеет 3 элемента, и позиция находится на втором элементе ($ this-> position = 1) перед foreach (),
После foreach () позиция изменяется на недопустимое значение ($ this-> position = 3).
class myIterator implements Iterator {
private $position = 0;
private $array = array(
"firstelement",
"secondelement",
"lastelement",
);
public function __construct() {
$this->position = 0;
}
public function rewind() {
var_dump(__METHOD__);
$this->position = 0;
}
public function current() {
var_dump(__METHOD__);
return $this->array[$this->position];
}
public function key() {
var_dump(__METHOD__);
return $this->position;
}
public function next() {
var_dump(__METHOD__);
++$this->position;
}
public function valid() {
var_dump(__METHOD__);
return isset($this->array[$this->position]);
}
public function showPosition() {
return $this->position;
}
}
$it = new myIterator;
$it->next();
var_dump($it->showPosition()); //shows 1
foreach($it as $key => $value) {
var_dump($key, $value);
echo "\n"; }
var_dump($it->showPosition()); //shows 3 which is an invalid value.
в foreach () документ это показывает:
Примечание: в PHP 5 …. В PHP 7 foreach не использует внутренний указатель массива.
Я использую PHP7, и, очевидно, приведенные выше примеры кода показывают, что внутренняя точка изменилась после foreach ().
Мой вопрос — возможно ли сохранить исходную позицию после foreach ()?
Я понимаю, что одним из возможных способов является добавление переменной для запоминания позиции перед foreach () и после foreach () установки позиции вручную. Но это противоречит тому, что предлагает документ foreach ().
Указатель в вашей реализации Itterator неверен после цикла, потому что вы не устанавливаете / сбрасываете значение при последнем вызове valid()
из цикла foreach он сохраняет последнее введенное значение, которое находится за пределами массива (в этом случае 3
), здесь проще всего установить указатель null
поэтому он отражает поведение PHP (что вы получите, когда используете встроенные функции массива внутри итератора, подробнее об этом позже)
Стоит отметить, что в PHP7 произошли изменения
Примечание: в PHP 5, когда foreach впервые начинает выполняться, внутренний
указатель массива автоматически сбрасывается на первый элемент
массив. Это означает, что вам не нужно вызывать reset () перед
цикл foreach. Поскольку foreach полагается на внутренний указатель массива в PHP
5, изменение его в цикле может привести к неожиданному поведению. В PHP
7, foreach не использует внутренний указатель массива.
Лично я не вижу в этом большой проблемы, если вы не передаете внутренний указатель. Что, по моему опыту, не так много.
Теперь, чтобы исправить вашу реализацию Iterator:
Опция 1:
Позвольте PHP обрабатывать отслеживание указателя с помощью встроенных функций массива:
class myIterator implements Iterator {
private $array = array(
"firstelement",
"secondelement",
"lastelement",
);
public function __construct() {
reset( $this->array );
}
public function rewind() {
reset( $this->array );
}
public function current() {
return current( $this->array );
}
public function key() {
return key( $this->array );
}
public function next() {
next( $this->array );
}
public function valid() {
return isset( $this->array[$this->key()] );
}
//alias of key()
public function showPosition() {
return $this->key();
}
}echo str_pad('= NATIVE TRACKED POINTER =', 60, '=') ."\n";
$it = new myIterator;
$it->next(); //move to index 1
var_dump($it->key()); //<-- should print 1
foreach($it as $key => $value) {}
var_dump($it->key()); //<--shows NULL,
echo str_pad('= NATIVE ARRAY POINTER =', 60, '=') ."\n";
$array = array(
"firstelement",
"secondelement",
"lastelement",
);
next($array); //move to index 1
var_dump(key($array)); //<-- should print 1
foreach( $array as $key => $value ){}
var_dump(key($array)); //<-- NULL (PHP < 7), 1 PHP 7+
Это выводит (в PHP7.0.1)
= NATIVE TRACKED POINTER ===================================
int(1)
NULL
= NATIVE ARRAY POINTER =====================================
int(1)
NULL
Это выводит (в PHP5.6.29)
= MANUAL TRACKED POINTER ===================================
int(1)
NULL
= NATIVE ARRAY POINTER =====================================
int(1)
NULL
Option2:
Обязательно сбросьте $position
в $myIterator->valid()' when the pointer is out of bounds. (This should be done after the call to
действительный ()you could check on next, by setting the pointer to
NULL` мы можем, по крайней мере, подражать поведению, которое вы бы получили, используя отслеживание родного указателя)
class myIterator implements Iterator {
private $position = 0;
private $array = array(
"firstelement",
"secondelement",
"lastelement",
);
public function __construct() {
$this->position = 0;
}
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() {
$valid = isset($this->array[$this->position]);
if(!$valid){
$this->position = null;
}
return $valid;
}
public function showPosition() {
return $this->position;
}
}
echo str_pad('= MANUAL TRACKED POINTER =', 60, '=') ."\n";
$it = new myIterator;
$it->next(); //move to index 1
var_dump($it->key()); //<-- should print 1
foreach($it as $key => $value) {}
var_dump($it->key()); //<--shows NULL (PHP < 7),
echo str_pad('= NATIVE ARRAY POINTER =', 60, '=') ."\n";
$array = array(
"firstelement",
"secondelement",
"lastelement",
);
next($array); //move to index 1
var_dump(key($array)); //<-- should print 1
foreach( $array as $key => $value ){}
var_dump(key($array)); //<-- NULL (PHP < 7), 1 PHP 7+
Для Option2 я настроил его так, чтобы он отражал нативное поведение PHP < 7, потому что вы вручную отслеживаете указатель массива, нет простого способа сделать это только с Iterator
интерфейс.
http://sandbox.onlinephpfunctions.com/code/2d38af15e5b0269dcdd341d0a77b601ce2713cce
Это выводит (в PHP7.0.1)
= MANUAL TRACKED POINTER ===================================
int(1)
int(0)
= NATIVE ARRAY POINTER =====================================
int(1)
int(1)
Это выводит (в PHP5.6.29)
= MANUAL TRACKED POINTER ===================================
int(1)
NULL
= NATIVE ARRAY POINTER =====================================
int(1)
NULL
ОБНОВИТЬ
Теперь, если вы хотите полностью эмулировать поведение PHP7, потребуется гораздо больше работы. (Я уверен, что может быть другой способ сделать это, но это способ, которым я придумал)
class myIterator implements IteratorAggregate{
protected $innerIterator = [];
public function __construct(array $array = []){
$this->innerIterator = new myInnerIterator($array,$this);
}
public function getIterator(){
$this->innerIterator->cachePointer();
return $this->innerIterator;
}
public function seek($index){
$this->innerIterator->seek($index);
}
public function rewind() {
$this->innerIterator->rewind();
}
public function current() {
return $this->innerIterator->current();
}
public function key() {
return $this->innerIterator->key();
}
public function next() {
$this->innerIterator->next();
}
public function valid() {
return $this->innerIterator->valid();
}
}
class myInnerIterator implements SeekableIterator{
protected $array;
protected $pointer_cache = null;
public function __construct( array $array = [], $wrapper){
if( !is_a($wrapper, 'myIterator') ) throw new Exception('myInnerIterator can only be constructed by myIterator');
$this->array = new ArrayIterator($array);
}
public function cachePointer(){
$this->pointer_cache = $this->key();
}
public function seek($index){
$this->array->seek($index);
}
public function rewind() {
$this->array->rewind();
}
public function current() {
return $this->array->current();
}
public function key() {
return $this->array->key();
}
public function next() {
$this->array->next();
}
public function valid() {
$valid = $this->array->valid();
if(!$valid && $this->pointer_cache ){
if( defined('PHP_VERSION_ID') && PHP_VERSION_ID >= 70000 )
$this->seek( $this->pointer_cache );
$this->pointer_cache = null;
}
return $valid;
}
}
echo str_pad('= CACHED POINTER =', 60, '=') ."\n";
$array = array(
"firstelement",
"secondelement",
"lastelement",
);
$it = new myIterator($array);
$it->next(); //move to index 1
var_dump($it->key()); //<-- should print 1
foreach($it as $key => $value) {}
var_dump($it->key()); //<--shows NULL,
echo str_pad('= NATIVE ARRAY POINTER =', 60, '=') ."\n";
next($array); //move to index 1
var_dump(key($array)); //<-- should print 1
foreach( $array as $key => $value ){}
var_dump(key($array)); //<-- NULL (PHP < 7), 1 PHP 7+
И это выводит
Это выводит (в PHP7.0.1)
= CACHED POINTER ===================================
int(1)
int(1)
= NATIVE ARRAY POINTER =====================================
int(1)
int(1)
Это выводит (в PHP5.6.29)
= CACHED POINTER ===================================
int(1)
NULL
= NATIVE ARRAY POINTER =====================================
int(1)
NULL
Вот песочница, где вы можете проверить это
http://sandbox.onlinephpfunctions.com/code/66dc26003347ec40184bb0283e78a3d67e86c51a
Таким образом, вы никогда не должны касаться myInnerIterator
класс, потому что он завернут в myIterator
на самом деле, если вы попытаетесь создать его, не передавая ему экземпляр myIterator, он выдаст исключение. Хитрость тебе нужна IteratorAggregate::getIterator
Который называется, когда foreach
начинается, а затем SeekableIterator::seek
Кроме того, кэширование позиции должно быть сделано в InnerIterator. Что еще называет getIterator
автоматически, и что все это будет влиять, я действительно не знаю с макушкой.
В любом случае, надеюсь, что это помогает или дает вам некоторые идеи.
Других решений пока нет …