Ссылки внутри цикла foreach в Php
Tue, Dec 15, 2015Тот случай, когда php работает согласно спецификации, но вопреки здравому смыслу.
Ниже приведён пример отлично работающего кода:
$list = array(1, 2, 3);
foreach ($list as &$item) {
$item++;
}
print_r($list);
Запускаем, получаем логичный вывод:
Array
(
[0] => 2
[1] => 3
[2] => 4
)
А теперь лёгким движением руки этот код превращается… превращается… в конструкцию со странным неочевидным функционалом.
$list = array(1, 2, 3);
foreach ($list as &$item) {
$item++;
}
print_r($list);
foreach ($list as $item) {
;
}
print_r($list);
Мы всего лишь организовали второй цикл foreach по одному и тому массиву. И в этот раз print_r
выдал совсем иные результаты.
Array
(
[0] => 2
[1] => 3
[2] => 3
)
Что же произошло? Собственно, ничего такого, чтобы мы сами не просили сделать PHP. В первом цикле мы объявили ссылку &$item, которая после завершения работы цикла указывает на элемент массива $list[2]. Далее мы пробегаемся ещё раз по массиву, на каждом шаге присваивая переменной $item очередное значение. Т.к. в PHP область видимости переменных не ограничивается блоком составного оператора, то переменная $item во втором цикле - это та же самая переменная из первого цикла. Поэтому, одновременно с установкой значения переменной $item, это же значение присваивается и элементу $list[2].
- Шаг 0: $item = $list[2] = $list[0] = 2
- Шаг 1: $item = $list[2] = $list[1] = 3
- Шаг 2: $item = $list[2] = $list[2] = 3
Никаких ошибок нет, но получить-то мы хотели несколько другой результат. Есть несколько вариантов, чтобы обезопасить себя от таких неожиданностей. Первый вариант - никогда не забываем принудительно чистить переменные. Вот этот код всегда работает так, как задумано.
$list = array(1, 2, 3);
foreach ($list as &$item) {
$item++;
}
print_r($list);
unset($item); // !!!
foreach ($list as $item) {
;
}
print_r($list);
Второй вариант - для изменения элементов массива не используем ссылки, используем конструкцию $key => $value. С ней также нет никаких проблем.
$list = array(1, 2, 3);
foreach ($list as $key => $item) {
$list[$key]++;
}
print_r($list);
foreach ($list as $key => $item) {
;
}
print_r($list);
Ну и самый правильный вариант, при котором мы не только избегаем описанной проблемы, но и многих других - максимально сокращаем область видимости переменных, выделяя логические части кода в отдельные функции. Этот вариант неплохо бы совмещать с одним из первых двух.
$list = array(1, 2, 3);
$incrementList = function ($list) {
foreach ($list as $key => $item) {
$list[$key]++;
}
return $list;
};
$list = $incrementList($list);
print_r($list);
$doList = function ($list) {
foreach ($list as $key => $item) {
;
}
return $list;
};
$list = $doList($list);
print_r($list);
Здесь $item первого цикла и $item второго цикла - разные переменные.
Пишите на PHP и прибудет с вами сила!