Я пробовал это как с простыми массивами, так и с итераторами PHP.К сожалению, PHP-итераторы, поскольку они являются объектами, работают по-другому.Объекты передаются по ссылке, а массивы - по значению.Поэтому, когда вложенный foreach достигает конца итератора, первый foreach не может возобновить с того места, где он остановился, поскольку внутренний указатель установлен на последний элемент.
Рассмотрим следующий пример, написанный с использованием простого массива PHP:
$test = [1, 2, 3];
foreach ($test as $i1 => $v1) {
echo "first loop: $i1\n";
foreach ($test as $i2 => $v2) {
echo "second loop: $i2\n";
}
}
Приведенный выше фрагмент кода дает следующий вывод:
first loop: 0
second loop: 0
second loop: 1
second loop: 2
first loop: 1
second loop: 0
second loop: 1
second loop: 2
first loop: 2
second loop: 0
second loop: 1
second loop: 2
Если мы попробуем то же самое с итератором, мы получим совершенно другой результат.Чтобы избежать путаницы, я буду использовать класс ArrayIterator , чтобы все уже реализовано ребятами из PHP, и мы не использовали интерфейсы неправильно.Поэтому здесь нет места ошибкам, вот как они реализуют итераторы:
$test = new ArrayIterator([1, 2, 3]);
foreach ($test as $i1 => $v1) {
echo "first loop: $i1\n";
foreach ($test as $i2 => $v2) {
echo "second loop: $i2\n";
}
}
Вывод:
first loop: 0
second loop: 0
second loop: 1
second loop: 2
Как вы можете видеть, первый foreach выполняется только один раз.
Обходным путем может быть реализация интерфейса SeekableIterator .Это позволило бы нам использовать метод seek () для сброса внутреннего указателя на его правильное значение.На мой взгляд, это плохая практика, но если парни из PHP не исправят это, я не могу сказать, что может быть лучше.Вероятно, с этого момента я бы избегал итераторов, поскольку они, кажется, ведут себя не так, как массивы, что, как мне кажется, поначалу предполагают люди.Поэтому их использование может привести к ошибкам в моем приложении, так как может случиться так, что разработчик в моей команде не знает об этом и не работает с кодом.
Следует примеру с интерфейсом SeekableIterator :
class MyIterator implements SeekableIterator
{
private $position = 0;
private $array = [1, 2, 3];
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()
{
return isset($this->array[$this->position]);
}
public function seek($position)
{
$this->position = $position;
}
}
$test = new MyIterator();
foreach ($test as $i1 => $v1) {
echo "first loop $i1\n";
foreach ($test as $i2 => $v2) {
echo "second loop $i2\n";
}
$test->seek($i1);
}
Вывод соответствует ожиданиям:
first loop: 0
second loop: 0
second loop: 1
second loop: 2
first loop: 1
second loop: 0
second loop: 1
second loop: 2
first loop: 2
second loop: 0
second loop: 1
second loop: 2
Все это происходит потому, что каждый foreach работает со своей собственной копией массива.Итераторы, поскольку они являются объектами, передаются по ссылке.Поэтому каждый foreach разделяет один и тот же объект.То же самое происходит, если вы пытаетесь сбросить элемент во вложенном foreach.Unset будет увеличивать внутренний указатель.Затем выполнение достигает конца вложенного foreach, и внутренний указатель снова увеличивается.Это означает, что при неустановке мы увеличиваем внутренний указатель в два раза.Поэтому родительский foreach пропустит элемент.
Мой совет: если вы не можете избежать итераторов, будьте ДЕЙСТВИТЕЛЬНО ДЕЙСТВИТЕЛЬНО осторожны.Всегда тщательно проверяйте их.
Примечание: Код, протестированный на PHP 5.6.14 и PHP 7.0.0 RC5.