Многие любят писать такие конструкции в том или ином виде, каждый сталкивался:
Но не многие подозревают о том, какая опасность тут скрывается.
Рассмотрим пример.
Вася Пупкин взял массив, прошелся по нему, увеличив на два все элементы:
Посмотрел дамп, увидел что задача решена, и ушел довольный:
Спустя некоторое время, Петрович решил дополнить этот участок кода другим перебором, дописав ниже:
Посмотрел, что его задача тоже решена, и с чувством выполненного долга закрыл файл:
Спустя какое-то время, стали вылезать необъяснимые баги. Почему?
Сделаем в конце кода var_dump($items):
30! Вася Пупкин клянётся, что проверял. Почему было 32, а после кода Петровича 30?
Причина кроется в амперсанде. Он сообщает, что на отмеченные данные ссылается кто-то ещё. Уходя, Вася не подтёр за собой временную переменную, которую использовал для перебора ($item). Переменная использовалась с разрешением на изменение источника ("&"), которое также называют «присваиванием по ссылке». Он был уверен, что переменная будет использоваться только внутри цикла. Петрович, используя переменную с таким же именем, в ходе своего перебора, менял её значение, и каждый раз менялось то место, где эта переменная хранилась. А хранилась она там же, где последний элемент массива Пупкина.
Конечно, в случай в статье утрирован. На практике такие связи могут быть очень сложными, особенно если проект недорогой, и в нём участвуют недостаточно опытные и разрозненные веб-разработчики.
Как можно с этим оброться?
В заключение скажу, что баги, связанные со ссылками, могут быть не только в foreach. И все они когда-то обсуждались. Однако, этот случай, судя по моему опыту, так распространён на практике, что заслуживает отдельного внимания.
foreach ($items as &$item) {
$item += 2;
}
Но не многие подозревают о том, какая опасность тут скрывается.
Рассмотрим пример.
Вася Пупкин взял массив, прошелся по нему, увеличив на два все элементы:
$items = array(
'a' => 10,
'b' => 20,
'c' => 30,
);
foreach ($items as &$item) {
$item += 2;
}
print_r($items);
Посмотрел дамп, увидел что задача решена, и ушел довольный:
Array
(
[a] => 12
[b] => 22
[c] => 32
)
Спустя некоторое время, Петрович решил дополнить этот участок кода другим перебором, дописав ниже:
$newitems = array(
'a' => 10,
'b' => 20,
'c' => 30,
);
foreach ($newitems as $key=>$item) {
$newitems[$key] += 5;
}
print_r($newitems);
Посмотрел, что его задача тоже решена, и с чувством выполненного долга закрыл файл:
Array
(
[a] => 15
[b] => 25
[c] => 35
)
Спустя какое-то время, стали вылезать необъяснимые баги. Почему?
Сделаем в конце кода var_dump($items):
array(3) {
["a"]=>
int(12)
["b"]=>
int(22)
["c"]=>
&int(30)
}
30! Вася Пупкин клянётся, что проверял. Почему было 32, а после кода Петровича 30?
Причина кроется в амперсанде. Он сообщает, что на отмеченные данные ссылается кто-то ещё. Уходя, Вася не подтёр за собой временную переменную, которую использовал для перебора ($item). Переменная использовалась с разрешением на изменение источника ("&"), которое также называют «присваиванием по ссылке». Он был уверен, что переменная будет использоваться только внутри цикла. Петрович, используя переменную с таким же именем, в ходе своего перебора, менял её значение, и каждый раз менялось то место, где эта переменная хранилась. А хранилась она там же, где последний элемент массива Пупкина.
Конечно, в случай в статье утрирован. На практике такие связи могут быть очень сложными, особенно если проект недорогой, и в нём участвуют недостаточно опытные и разрозненные веб-разработчики.
Как можно с этим оброться?
- Уничтожать временные переменные после использования, особенно если они имеют какие-то связи с используемыми данными:
foreach ($items as &$item) $item += 2; unset($item);
- Быть осторожнее с переменными, которые уже кем-то использовались.
- Инкапсулировать свои действия в отдельные функции, методы или пространства имён.
- Использовать var_dump, вместо print_r, и обращать внимание на амперсанд. Чтобы дампить в файл, а не в браузер, альтернативой print_r($var,true) будет такая конструкция:
function dump() { ob_start(); foreach(func_get_args() as $var) var_dump($var); return ob_get_clean(); }
В заключение скажу, что баги, связанные со ссылками, могут быть не только в foreach. И все они когда-то обсуждались. Однако, этот случай, судя по моему опыту, так распространён на практике, что заслуживает отдельного внимания.