PHP7 - изменения в foreach
Категория: / DEV Блог
/ PHP (LAMP)
В PHP5 присутствует неоднозначность в работе foreach, которая возникает из-за манипуляции с внутренним указателем массива или модификации элементов массива, который перебирается в foreach.
Поведение foreach может зависить от ссылочного счетчика или типа итерируемого объекта - является он значением или ссылкой.
Вот несколько примеров foreach
1. Неработающий current()
2. unset() может исключить элемент из перебора, а может и нет
В PHP7 реализован более рациональный подход.
Первое, foreach может итерировать:
- по значению - foreach ($a as $v)
- по ссылке - foreach ($a as &$v)
Второе, foreach может перебирать
- массив $x = [1,2,3]; foreach ($x as $v)
- простой объект - $x = new SomeClass(); foreach ($x as $v)
- итератор - $x = new SomeIteratorClass(); foreach ($x as $v)
Итерация массива по значению
Никогда не использует и меняет внутренний указатель массива.
Не делает копию массива, а лочит оригинальный массив (реф.счетчик++)
Модификации оригинального массива игнорируются
Итерация массива по ссылке
В основном повторяет поведение PHP5.
Изменяет внутренний указатель (передвигает его на следующий элемент)
@wtf: и где он его изменяет, нужно уточнение?
Модификация внутреннего указателя функциями next(), reset(), end() не отражаются на указателе foreach,
при следующей итерации foreach восстановит свой указатель (как в PHP5)
Удаление следующего элемента (указателя foreach) удалит его из перебора
Добавление новых элементов после указателя, добавит их в перебор
Добавление новых элементов после указателя (когда мы находимся в конце массива), все равно добавит их в перебор
В случае использования нескольких ссылочного foreach по одному и тому же массиву, работают теже правила, независимо друг от друга (не работает в PHP5)
Возможно изменение массива, итерируемого в ссылочном foreach используя функции array_pop(), array_push(), array_shift(), array_unshift()
Эти функции сохраняют указатель массива foreach или передвигают его к следующему элементу, если текущий удален (не работает в PHP5)
Итерация простых объектов по значению
Работает также как итерация массива по ссылке, но используется значение объекта, вместо ссылки, как результат объект может быть изменен, НО не заменен.
Итерация простых объектов по ссылке
Работает также как итерация массива по ссылке
@Ссылки
RFC: Fix "foreach" behavior
Миграция с PHP5.X -> PHP7
Онлайн выполнение PHP кода для тестирования всех версий PHP
Поведение foreach может зависить от ссылочного счетчика или типа итерируемого объекта - является он значением или ссылкой.
Вот несколько примеров foreach
1. Неработающий current()
$a = [1,2,3]; foreach($a as $v) {echo $v . " - " . current($a) . "\n";}
1 - 2
2 - 2
3 - 2
$a = [1,2,3]; $b = &$a; foreach($a as $v) {echo $v . " - " . current($a) . "\n";}
1 - 2
2 - 3
3 -
$a = [1,2,3]; $b = $a; foreach($a as $v) {echo $v . " - " . current($a) . "\n";}
1 - 1
2 - 1
3 - 1
2. unset() может исключить элемент из перебора, а может и нет
$a = [1,2,3]; foreach($a as $v) {echo "$v\n"; unset($a[1]);}
1
2
3
$a = [1,2,3]; $b = &$a; foreach($a as $v) {echo "$v\n"; unset($a[1]);}
1
3
В PHP7 реализован более рациональный подход.
Первое, foreach может итерировать:
- по значению - foreach ($a as $v)
- по ссылке - foreach ($a as &$v)
Второе, foreach может перебирать
- массив $x = [1,2,3]; foreach ($x as $v)
- простой объект - $x = new SomeClass(); foreach ($x as $v)
- итератор - $x = new SomeIteratorClass(); foreach ($x as $v)
Итерация массива по значению
Никогда не использует и меняет внутренний указатель массива.
Не делает копию массива, а лочит оригинальный массив (реф.счетчик++)
$a = [1,2,3]; foreach($a as $v) {echo $v . " - " . current($a) . "\n";
// до PHP 7
1 - 2
2 - 2
3 - 2
// PHP 7+
1 - 1
2 - 1
3 - 1
Модификации оригинального массива игнорируются
$a = [1,2,3]; $b = &$a; foreach($a as $v) {echo "$v\n"; unset($a[1]);}
// до PHP 7 result
1
3
// PHP 7+
1
2
3
Итерация массива по ссылке
В основном повторяет поведение PHP5.
Изменяет внутренний указатель (передвигает его на следующий элемент)
$a = [1,2,3]; foreach($a as &$v) {echo $v . " - " . current($a) . "\n"; }
// до PHP 7
1 - 2
2 - 3
3 -
// PHP 7+
1 - 1
2 - 1
3 - 1
в RFC результаты PHP7 отличаются
1 - 2
2 - 3
3 -
@wtf: и где он его изменяет, нужно уточнение?
Модификация внутреннего указателя функциями next(), reset(), end() не отражаются на указателе foreach,
при следующей итерации foreach восстановит свой указатель (как в PHP5)
$a = [1,2,3,4]; foreach($a as &$v) {echo "$v - "; next($a); var_dump(current($a));}
// PHP 5.4 - 7+
1 - int(3)
2 - int(4)
3 - bool(false)
4 - bool(false)
Удаление следующего элемента (указателя foreach) удалит его из перебора
$a = [1,2,3]; foreach($a as &$v) {echo "$v\n"; unset($a[1]);}
// PHP 5.4 - 7+
1
3
Добавление новых элементов после указателя, добавит их в перебор
$a = [1,2]; foreach($a as &$v) {echo "$v\n"; $a[2]=3;}
// PHP 5.4 - 7+
1
2
3
Добавление новых элементов после указателя (когда мы находимся в конце массива), все равно добавит их в перебор
$a = [1,2]; $b = [3,4]; next($b); foreach($a as &$v) {echo "$v\n"; $a = $b;}
// PHP 5.4 - 7+
1
4
// hhvm фейлит данный тест с бесконечным циклом
В случае использования нескольких ссылочного foreach по одному и тому же массиву, работают теже правила, независимо друг от друга (не работает в PHP5)
$a = [0, 1, 2, 3];
foreach ($a as &$x) {
foreach ($a as &$y) {
echo "$x - $y\n";
if ($x == 0 && $y == 1) {
unset($a[1]);
unset($a[2]);
}
}
}
// PHP5.4
0 - 0
0 - 1
0 - 3
// PHP 7+
0 - 0
0 - 1
0 - 3
3 - 0
3 - 3
Возможно изменение массива, итерируемого в ссылочном foreach используя функции array_pop(), array_push(), array_shift(), array_unshift()
Эти функции сохраняют указатель массива foreach или передвигают его к следующему элементу, если текущий удален (не работает в PHP5)
$a=[1,2,3,4]; foreach($a as &$v) { echo "$v\n"; array_pop($a);}
// PHP5.4
1
2
1
1
// PHP 7+
1
2
Итерация простых объектов по значению
Работает также как итерация массива по ссылке, но используется значение объекта, вместо ссылки, как результат объект может быть изменен, НО не заменен.
Итерация простых объектов по ссылке
Работает также как итерация массива по ссылке
@Ссылки
RFC: Fix "foreach" behavior
Миграция с PHP5.X -> PHP7
Онлайн выполнение PHP кода для тестирования всех версий PHP
Пока вы не изменяете итерируемый массив, работа происходит с оригинальным массивом. При попытке изменения массива (в цикле) сработает copy-on-write и будет создана копия.
Немного не понятно, у Вас написано,
"Итерация массива по значению
Не делает копию массива, а лочит оригинальный массив (реф.счетчик++)"
однако на сайте "http://php.net/manual/ru/migration70.incompatible.php" написано -
"foreach by-value operates on a copy of the array ¶
When used in the default by-value mode, foreach will now operate on a copy of the array being iterated rather than the array itself. This means that changes to the array made during iteration will not affect the values that are iterated."
(Когда используется по значению, foreach теперь будет обрабатывать копию массива, а не сам массив...)
Где правда?