Цикл PHP foreach: два способа его использования. О тонкостях работы foreach в PHP Php массивы foreach


Цикл For Each... Next в VBA Excel, его синтаксис и описание отдельных компонентов. Примеры использования цикла For Each... Next.

Цикл For Each... Next в VBA Excel предназначен для выполнения блока операторов по отношению к каждому элементу из группы элементов (диапазон, массив, коллекция). Этот замечательный цикл применяется, когда неизвестно количество элементов в группе и их индексация, в противном случае, более предпочтительным считается использование .

Синтаксис цикла For Each... Next

For Each element In group [ statements ] [ Exit For ] [ statements ] Next [ element ]

В квадратных скобках указаны необязательные атрибуты цикла For Each... Next.

Компоненты цикла For Each... Next

*Если цикл For Each... Next используется в VBA Excel для прохождения элементов коллекции (объект Collection) или массива, тогда переменная element должна быть объявлена с типом данных Variant , иначе цикл работать не будет.

**Если не использовать в цикле свой код, смысл применения цикла теряется.

Примеры циклов For Each... Next

Цикл для диапазона ячеек

На активном листе рабочей книги Excel выделите диапазон ячеек и запустите на выполнение следующую процедуру:

Sub test1() Dim element As Range, a As String a = "Данные, полученные с помощью цикла For Each... Next:" For Each element In Selection a = a & vbNewLine & "Ячейка " & element.Address & _ " содержит значение: " & CStr(element.Value) Next MsgBox a End Sub

Информационное окно MsgBox выведет адреса выделенных ячеек и их содержимое, если оно есть. Если будет выбрано много ячеек, то полностью информация по всем ячейкам выведена не будет, так как максимальная длина параметра Prompt составляет примерно 1024 знака.

Цикл для коллекции листов

Скопируйте следующую процедуру VBA в книги Excel:

Sub test2() Dim element As Worksheet, a As String a = "Список листов, содержащихся в этой книге:" For Each element In Worksheets a = a & vbNewLine & element.Index _ & ") " & element.Name Next MsgBox a End Sub

Информационное окно MsgBox выведет список наименований всех листов рабочей книги Excel по порядковому номеру их ярлычков, соответствующих их индексам.

Цикл для массива

Присвоим массиву список наименований животных и в цикле For Each... Next запишем их в переменную a . Информационное окно MsgBox выведет список наименований животных из переменной a .

Sub test3() Dim element As Variant, a As String, group As Variant group = Array("бегемот", "слон", "кенгуру", "тигр", "мышь") "или можно присвоить массиву значения диапазона ячеек "рабочего листа, например, выбранного: group = Selection a = "Массив содержит следующие значения:" & vbNewLine For Each element In group a = a & vbNewLine & element Next MsgBox a End Sub

Повторим ту же процедуру VBA, но всем элементам массива в цикле For Each... Next присвоим значение «Попугай». Информационное окно MsgBox выведет список наименований животных, состоящий только из попугаев, что доказывает возможность редактирования значений элементов массива в цикле For Each... Next.

Sub test4() Dim element As Variant, a As String, group As Variant group = Array("бегемот", "слон", "кенгуру", "тигр", "мышь") "или можно присвоить массиву значения диапазона ячеек "рабочего листа, например, выделенного: group = Selection a = "Массив содержит следующие значения:" & vbNewLine For Each element In group element = "Попугай" a = a & vbNewLine & element Next MsgBox a End Sub

Этот код, как и все остальные в этой статье, тестировался в Excel 2016.

Цикл для коллекции подкаталогов и выход из цикла

В этом примере мы будем добавлять в переменную a названия подкаталогов на диске C вашего компьютера. Когда цикл дойдет до папки Program Files , он добавит в переменную a ее название и сообщение: «Хватит, дальше читать не буду! С уважением, Ваш цикл For Each... Next.».

Sub test5() Dim FSO As Object, myFolders As Object, myFolder As Object, a As String "Создаем новый FileSystemObject и присваиваем его переменной "FSO" Set FSO = CreateObject("Scripting.FileSystemObject") "Извлекаем список подкаталогов на диске "C" и присваиваем "его переменной "myFolders" Set myFolders = FSO.GetFolder("C:\") a = "Папки на диске C:" & vbNewLine "Проходим циклом по списку подкаталогов и добавляем в переменную "a" "их имена, дойдя до папки "Program Files", выходим из цикла For Each myFolder In myFolders.SubFolders a = a & vbNewLine & myFolder.Name If myFolder.Name = "Program Files" Then a = a & vbNewLine & vbNewLine & "Хватит, дальше читать не буду!" _ & vbNewLine & vbNewLine & "С уважением," & vbNewLine & _ "Ваш цикл For Each... Next." Exit For End If Next Set FSO = Nothing MsgBox a End Sub

Информационное окно MsgBox выведет список наименований подкаталогов на диске C вашего компьютера до папки Program Files включительно и сообщение цикла о прекращении своей работы.

В результате работы программы будут выведены не только наименования подкаталогов, видимых при переходе в проводнике к диску C , но и скрытые и служебные папки. Для просмотра списка всех подкаталогов на диске C , закомментируйте участок кода от If до End If включительно и запустите выполнение процедуры в редакторе VBA Excel.

(PHP 4, PHP 5, PHP 7)

Конструкция foreach предоставляет простой способ перебора массивов. Foreach работает только с массивами и объектами, и будет генерировать ошибку при попытке использования с переменными других типов или неинициализированными переменными. Существует два вида синтаксиса:

foreach (array_expression as $value) statement foreach (array_expression as $key => $value) statement

Первый цикл перебирает массив, задаваемый с помощью array_expression . На каждой итерации значение текущего элемента присваивается переменной $value и внутренний указатель массива увеличивается на единицу (таким образом, на следующей итерации цикла работа будет происходить со следующим элементом).

Второй цикл будет дополнительно соотносить ключ текущего элемента с переменной $key на каждой итерации.

Замечание :

Когда оператор foreach начинает исполнение, внутренний указатель массива автоматически устанавливается на первый его элемент Это означает, что нет необходимости вызывать функцию reset() перед использованием цикла foreach .

Так как оператор foreach опирается на внутренний указатель массива, его изменение внутри цикла может привести к непредсказуемому поведению.

Для того, чтобы напрямую изменять элементы массива внутри цикла, переменной $value должен предшествовать знак &. В этом случае значение будет присвоено по ссылке .

$arr = array(1 , 2 , 3 , 4 );
foreach ($arr as & $value ) {
$value = $value * 2 ;
}
// массив $arr сейчас таков: array(2, 4, 6, 8)
unset($value ); // разорвать ссылку на последний элемент
?>

Указатель на $value возможен, только если на перебираемый массив можно ссылаться (т.е. если он является переменной). Следующий код не будет работать:

foreach (array(1 , 2 , 3 , 4 ) as & $value ) {
$value = $value * 2 ;
}
?>

Внимание

Ссылка $value на последний элемент массива остается даже после того, как оператор foreach завершил работу. Рекомендуется уничтожить ее с помощью функции unset() .

Замечание :

Оператор foreach не поддерживает возможность подавления сообщений об ошибках с помощью префикса "@".

Вы могли заметить, что следующие конструкции функционально идентичны:


reset ($arr );
while (list(, $value ) = each ($arr )) {
echo "Значение: $value
\n" ;
}

foreach ($arr as $value ) {
echo "Значение: $value
\n" ;
}
?>

Следующие конструкции также функционально идентичны:

$arr = array("one" , "two" , "three" );
reset ($arr );
while (list($key , $value ) = each ($arr )) {

\n" ;
}

foreach ($arr as $key => $value ) {
echo "Ключ: $key ; Значение: $value
\n" ;
}
?>

Вот еще несколько примеров, демонстрирующие использование оператора:

/* Пример 1: только значение */

$a = array(1 , 2 , 3 , 17 );

foreach ($a as $v ) {
echo "Текущее значение переменной \$a: $v .\n" ;
}

/* Пример 2: значение (для иллюстрации массив выводится в виде значения с ключом) */

$a = array(1 , 2 , 3 , 17 );

$i = 0 ; /* только для пояснения */

Foreach ($a as $v ) {
echo "\$a[ $i ] => $v .\n" ;
$i ++;
}

/* Пример 3: ключ и значение */

$a = array(
"one" => 1 ,
"two" => 2 ,
"three" => 3 ,
"seventeen" => 17
);

foreach ($a as $k => $v ) {
echo "\$a[ $k ] => $v .\n" ;
}

/* Пример 4: многомерные массивы */
$a = array();
$a [ 0 ][ 0 ] = "a" ;
$a [ 0 ][ 1 ] = "b" ;
$a [ 1 ][ 0 ] = "y" ;
$a [ 1 ][ 1 ] = "z" ;

foreach ($a as $v1 ) {
foreach ($v1 as $v2 ) {
echo " $v2 \n" ;
}
}

/* Пример 5: динамические массивы */

Foreach (array(1 , 2 , 3 , 4 , 5 ) as $v ) {
echo " $v \n" ;
}
?>

Распаковка вложенных массивов с помощью list()

(PHP 5 >= 5.5.0, PHP 7)

В PHP 5.5 была добавлена возможность обхода массива массивов с распаковкой вложенного массива в переменные цикла, передав list() в качестве значения.

Цикл PHP foreach можно использовать следующим образом:

foreach($array_name as $value){ //код, который должен выполняться }

foreach($array_name as $key =>$value){ // //код, который должен выполняться }

Пример использования цикла foreach с числовым массивом

В этом примере мы создадим массив из пяти элементов с числовыми значениями. После этого цикл PHP foreach будет использован для выполнения итерации этого массива. Внутри цикла foreach мы использовали echo , чтобы вывести значения массива:

Посмотреть демо-версию и код

Пример с ключами и значениями массива

В этом примере описан другой способ использования цикла foreach PHP . Для этого мы создали ассоциативный массив из трех элементов. В него входят имена сотрудников (в качестве ключей ) и суммы заработной платы (в качестве значений ):

Посмотреть демо-версию и код

Пример изменения значения элемента массива в цикле foreach

Также можно c помощью PHP array foreach можно изменять значения элементов массива. Для этого используется «& » перед «$ » для переменной значения. Например:

&$value_of_element

Значение будет изменено. Чтобы вам было понятнее, рассмотрим следующий пример.

В этом примере мы создали числовой массив из пяти элементов. После этого использовали цикл foreach для отображения значений элементов.

Затем создали еще один цикл foreach , где перед $value_of_element добавляется «& «. Внутри фигурных скобок присваиваем новые значения элементам массива.

Чтобы увидеть разницу до и после присвоения новых значений, массив отображается с помощью функции print_r() .

Посмотреть демо-версию и код

Для чего используется цикл PHP foreach?

Цикл PHP foreach используется для работы с массивом. Он перебирает каждый его элемент.

Также можно использовать для работы с массивами цикл for . Например, используя свойство length , чтобы получить длину массива, а затем применить его в качестве оператора max . Но foreach делает это проще, так как он предназначен для работы с массивами.

Если вы работаете с MySQL , то для этого данный цикл подходит еще больше. Например, можно выбрать несколько строк из таблицы БД и передать их в массив. После этого, используя цикл foreach , перебрать все элементы массива с выполнением какого-либо действия.

Обратите внимание, что можно использовать цикл foreach с массивом или только с объектом.

Применение цикла foreach

В PHP существует два способа использования цикла foreach PHP. Оба описаны ниже.

  • Синтаксис первого метода использования:

foreach($array_name as $value){ echo $value }

При этом нужно указать имя массива, а затем переменную $value .

Для каждой итерации значение текущего элемента присваивается переменной $value . После завершения итерации переменной присваивается значение следующего элемента. И так до тех пор, пока все элементы массива не будут перебраны.

  • Синтаксис второго метода (PHP foreach as key value ):

Это подходит для ассоциативных массивов, в которых используются пары ключ / значение.

Во время выполнения каждой итерации значение текущего элемента будет присвоено переменной $value_of_element. Кроме этого ключ элемента присваивается переменной $key_of_element.

Если вы работаете с числовыми массивами, то можно использовать первый метод, в котором не нужны ключи элементов.

Данная публикация представляет собой перевод статьи «PHP foreach loop 2 ways to use it » , подготовленной дружной командой проекта

В недавнем дайджесте интересных ссылок о PHP я обнаружил ссылку на комментарий Никиты Попова на StackOverflow, где он подробно рассказывает о механизме «под капотом» управляющей конструкции foreach.
Поскольку foreach действительно иногда работает более, чем странным образом, я счел полезным сделать перевод этого ответа.

Внимание: этот текст подразумевает наличие базовых знаний о функциональности zval"ов в PHP, в частности вы должны знать что такое refcount и is_ref.
foreach работает с сущностями разных типов: с массивами, с простыми объектами (где перечисляются доступные свойства) и с Traversable -объектами (вернее, объектами, у которых определен внутренний обработчик get_iterator). Здесь мы, в основном, говорим о массивах, но я скажу и об остальных в самом конце.

Прежде чем приступить, пара слов о массивах и их обходе, важная для понимания контекста.

Как работает обход массивов

Массивы в PHP являются упорядоченными хеш-таблицами (элементы хеша объединены в двусвязный список) и foreach обходит массив, следуя указанному порядку.

PHP включает в себя два способа обхода массива:

  • Первый способ - внутренний указатель массива. Этот указатель является частью структуры HashTable и представляет собой просто указатель на текущий элемент хеш-таблицы. Внутренний указатель массива можно безнаказанно изменять, то есть если текущий элемент удалён, внутренний указатель массива будет перемещен на следующий.
  • Второй механизм итерации - внешний указатель массива, под названием HashPosition. Это практически то же самое, что и внутренний указатель массива, но он не является частью HashTable. Этот внешний способ итерации не безопасен к изменениям. Если вы удалите элемент, на который указывает HashPosition, то останетесь с висячим указателем, что приведёт к ошибке сегментации.

Таким образом, внешние указатели массива могут быть использованы только когда вы полностью уверены, что при обходе никакого пользовательского кода выполняться не будет. А такой код может оказаться в самом неожиданном месте, типа обработчика ошибок или деструктора. Вот почему в большинстве случаев PHP приходится использовать внутренний указатель вместо внешнего. Если бы это было иначе, PHP мог бы упасть из-за segmentation fault, как только пользователь начнет делать что-нибудь необычное.

Проблема внутреннего указателя в том, что он является частью HashTable. Так что, когда вы изменяете его, HashTable меняется вместе с ним. И коль скоро обращение к массивам в PHP делается по значению (а не по ссылке), вы вынуждены копировать массив, чтобы в цикле обходить его элементы.

Простой пример, показывающий важность копирования (кстати, не такая большая редкость), это вложенная итерация:

Foreach ($array as $a) { foreach ($array as $b) { // ... } }

Здесь вы хотите чтобы оба цикла были независимым, а не хитро перебрасывались одним указателем.

Итак, мы дошли до foreach.

Обход массива в foreach

Теперь вы знаете, для чего foreach приходится создавать копию массива, прежде чем обойти его. Но это явно не вся история. Сделает PHP копию или нет, зависит от нескольких факторов:

  • Если итерируемый массив является ссылкой, копирования не произойдёт, вместо этого будет выполнен addref:

    $ref =& $array; // $array has is_ref=1 now foreach ($array as $val) { // ... }
    Почему? Потому что любое изменение массива должно распространяться по ссылке, включая внутренний указатель. Если бы foreach сделал копию в этом случае, он бы разрушил семантику ссылки.

  • Если массив имеет refcount=1, копирование, опять таки, не будет выполнено. refcount=1 означает, что массив не используется в другом месте и foreach может использовать его напрямую. Если refcount больше одного, значит массив разделен с другими переменными и для того чтобы избежать изменения, foreach должен скопировать его (независимо от случая ссылки, описанного выше).
  • Если массив обходится по ссылкам (foreach ($array as &$ref)), то - независимо от функции копирования или не-копирования - массив станет ссылкой.

Итак, это первая часть тайны: функция копирования. Вторая часть это то, как текущая итерация выполняется, и она тоже довольно странная. «Обычный» образец итерации, который вы уже знаете (и который часто используется в PHP - отдельно от foreach) выглядит примерно так (псевдокод):

Reset(); while (get_current_data(&data) == SUCCESS) { code(); move_forward(); }
итерация foreach выглядит немного иначе:

Reset(); while (get_current_data(&data) == SUCCESS) { move_forward(); code(); }

Отличие в том, что move_forward() выполняется в начале, а не в конце цикла. Таким образом, когда код пользователя использует элемент $i, внутренний указатель массива уже указывает на элемент $i+1.

Такой режим работы foreach также является причиной, по которой внутренний указатель массива переходит к следующему элементу, если текущий удалён, а не к предыдущему (как вы могли бы ожидать). Всё сделано так, чтобы отлично работать с foreach (но, очевидно, со всем остальным будет работать не так хорошо, пропуская элементы).

Последствия для кода

Первое следствие вышеописанного поведения в том, что foreach копирует итерируемый массив в многих случаях (медленно). Но отриньте страх: я пробовал удалить требование копирования и не смог увидеть ускорения работы нигде, кроме искусственных бенчмарков (в которых итерация происходила в два раза быстрее). Похоже, люди просто не итерируют достаточно много.

Второе следствие в том, что обычно не должно быть других следствий. Поведение foreach, в основном, вполне понятно пользователю и просто работает как следует. Вас не должно волновать, как происходит копирование (и происходит ли оно вообще), и в какой конкретно момент времени перемещается указатель.

И третье следствие - и тут мы как раз подходим к вашим проблемам - в том, что иногда мы видим очень странное поведение, которое трудно понять. Это происходит конкретно тогда, когда вы пытаетесь модифицировать сам массив, который вы обходите в цикле.

Большую коллекцию поведения в пограничных случаях, которые появляются, когда вы модифицируете массив в ходе итерации, можно найти в тестах PHP. Вы можете начать с этого теста , после чего изменять 012 на 013 в адресе, и так далее. Вы увидите, как поведение foreach будет проявляться в разных ситуациях (всякие комбинации ссылок и.т.д.).

А сейчас вернёмся к вашим примерам:

Foreach ($array as $item) { echo "$item\n"; $array = $item; } print_r($array); /* Output in loop: 1 2 3 4 5 $array after loop: 1 2 3 4 5 1 2 3 4 5 */

Здесь $array имеет refcount=1 до цикла, так что он не будет копирован, но получит addref. Как только вы присвоите значение $array, zval будет разделен, так что массив, к которому вы добавляете элементы и итерируемый массив будут двумя разными массивами.

Foreach ($array as $key => $item) { $array[$key + 1] = $item + 2; echo "$item\n"; } print_r($array); /* Output in loop: 1 2 3 4 5 $array after loop: 1 3 4 5 6 7 */

Та же ситуация, что и в первом тесте.

// Сдвигаем указатель на единицу, чтобы убедиться, что это не влияет на foreach var_dump(each($array)); foreach ($array as $item) { echo "$item\n"; } var_dump(each($array)); /* Output array(4) { => int(1) ["value"]=> int(1) => int(0) ["key"]=> int(0) } 1 2 3 4 5 bool(false) */

Снова та же история. Во время цикла foreach, у вас refcount=1 и вы получаете только addref, внутренний указатель $array будет изменён. В конце цикла указатель становится NULL (это означает что итерация закончена). each демонстрирует это, возвращая false.

Foreach ($array as $key => $item) { echo "$item\n"; each($array); } /* Output: 1 2 3 4 5 */

Foreach ($array as $key => $item) { echo "$item\n"; reset($array); } /* Output: 1 2 3 4 5 */

Но эти примеры недостаточно убедительны. Поведение начинает быть по настоящему непредсказуемым, когда вы используете current в цикле:

Foreach ($array as $val) { var_dump(current($array)); } /* Output: 2 2 2 2 2 */

Здесь вы должны иметь в виду, что current тоже обращается по ссылке, несмотря на то, что не изменяет массив. Это нужно, чтобы согласованно работать со всеми остальными функциями, вроде next, которые обращаются по ссылке (current, вообще-то, предпочтительно-ref функция; она может получить значение, но использует ссылку, если сможет). Ссылка означает, что массив должен быть отделён, следовательно, $array и копия $array, которую использует foreach, будут независимы. Почему вы получаете 2, а не 1, также упомянуто выше: foreach увеличивает указатель массива до начала кода пользователя , а не после. Так что, даже если код все еще работает с первым элементом, foreach уже переместил указатель ко второму.

Теперь попробуем сделать небольшое изменение:

$ref = &$array; foreach ($array as $val) { var_dump(current($array)); } /* Output: 2 3 4 5 false */

Здесь у нас is_ref=1, так что массив не копирован (так как и выше). Но сейчас когда есть is_ref, массив больше не нужно разделять, передавая по ссылке к current. Теперь current и foreach работают с одним массивом. Вы видите массив сдвинутым на единицу как раз из-за того, как foreach обращается с указателем.

То же самое вы увидите, когда будете делать обход массива по ссылкам:

Foreach ($array as &$val) { var_dump(current($array)); } /* Output: 2 3 4 5 false */

Здесь самое важное - то, что foreach назначит нашему $array is_ref=1, когда он будет обходить его в цикле по ссылке, так что получится то же, что и выше.

Еще одна небольшая вариация, здесь мы присвоим наш массив еще одной переменной:

$foo = $array; foreach ($array as $val) { var_dump(current($array)); } /* Output: 1 1 1 1 1 */

Здесь refcount массива $array принимает значение 2, когда цикл начался, так что нужно сделать копию, прежде чем начинать. Таким образом, $array и массив используемый foreach будут разными с самого начала. Вот почему вы получаете ту позицию внутреннего указателя массива, которая была актуальна до начала цикла (в этом случае он был в первой позиции).

Итерация объектов

При итерации объектов имеет смысл рассмотреть два случая:

Объект не Traversable (вернее, не определен внутренний обработчик get_iterator)

В этом случае итерация происходит почти так же, как у массивов. Та же семантика копирования. Единственное отличие: foreach запустит некий дополнительный код, чтобы пропустить свойства, недоступные в текущей области видимости. Еще пара интересных фактов:

  • Для объявленных свойств PHP реоптимизирует хеш-таблицу свойств. Если вы все-таки итерируете объект, он должен реконструировать эту хеш-таблицу (что повышает использование памяти). Не то, чтобы вам следовало беспокоиться об этом, просто имейте в виду.
  • На каждой итерации хеш-таблица свойств будет получена заново, то есть PHP будет вызывать get_properties снова, и снова, и снова. Для «обычных» свойств это не так важно, но если свойства создаются динамически (это часто делают встроенные классы) - то таблица свойств будет пересчитываться каждый раз.
Объект Traversable

В этом случае всё, что сказано выше, не будет применяться никоим образом. Также PHP не будет копировать и не будет применять никакие трюки вроде увеличения указателя до прохода цикла. Я думаю что режим прохода по обходимому (Traversable) объекту куда более предсказуем и не требует дальнейшего описания.

Замена итерируемого объекта во время цикла

Другой необычный случай, который я не упомянул - PHP допускает возможность замены итерируемого объекта во время цикла. Вы можете начать с одним массивом и продолжить, заменив его на полдороге другим. Или начать с массивом, в затем заменить его объектом:

$arr = ; $obj = (object) ; $ref =& $arr; foreach ($ref as $val) { echo "$val\n"; if ($val == 3) { $ref = $obj; } } /* Output: 1 2 3 6 7 8 9 10 */

Как видите, PHP просто начал обходить другую сущность, как только произошла замена.

Изменение внутреннего указателя массива во время итерации

Последняя деталь поведения foreach, которую я не упомянул (потому что может быть использована для получения по настоящему странного поведения ): что может случиться если попытаться изменить внутренний указатель массива во время прохода цикла.

Тут вы можете получить не то, что ожидали: если вызывать next или prev в теле цикла (в случае передачи по ссылке), вы увидите, что внутренний указатель переместился, но это никак не повлияло на поведение итератора. Причина в том, что foreach делает бекап текущей позиции и хеша текущего элемента в HashPointer после каждого прохода цикла. На следующей проходе foreach проверит, не менялась ли позиция внутреннего указателя и попытается восстановить ее, используя этот хеш.

Давайте посмотрим что означает «попытается». Первый пример показывает, как изменение внутреннего указателя не меняет режим foreach:

$array = ; $ref =& $array; foreach ($array as $value) { var_dump($value); reset($array); } // output: 1, 2, 3, 4, 5

Теперь давайте попробуем сделать unset элементу, к которому обратится foreach при первом проходе (ключ 1):

$array = ; $ref =& $array; foreach ($array as $value) { var_dump($value); unset($array); reset($array); } // output: 1, 1, 3, 4, 5

Тут вы увидите, что счетчик сброшен, так как не удалось найти элемент с подходящим хешом.

Имейте в виду, хеш - всего лишь хеш. Случаются коллизии. Попробуем теперь так:

$array = ["EzEz" => 1, "EzFY" => 2, "FYEz" => 3]; $ref =& $array; foreach ($array as $value) { unset($array["EzFY"]); $array["FYFZ"] = 4; reset($array); var_dump($value); } // output: 1 1 3 4

Работает так, как мы и ожидали. Мы удалили ключ EzFY (тот, где как раз был foreach), так что был сделан сброс. Также мы добавили дополнительный ключ, поэтому в конце мы видим 4.

И вот тут приходит неведомое. Что произойдёт, если заменить ключ FYFY с FYFZ? Давайте попробуем:

$array = ["EzEz" => 1, "EzFY" => 2, "FYEz" => 3]; $ref =& $array; foreach ($array as $value) { unset($array["EzFY"]); $array["FYFY"] = 4; reset($array); var_dump($value); } // output: 1 4

Сейчас цикл перешёл непосредственно к новому элементу, пропуская всё остальное. Это потому что ключ FYFY имеет коллизию с EzFY (вообще-то, все ключи из этого массива тоже). Более этого, элемент FYFY находится по тому же адресу в памяти, что и элемент EzFY который только что был удален. Так что для PHP это будет та же самая позиция с тем же хешом. Позиция «восстановлена» и происходит переход к концу массива.