Многие любят писать такие конструкции в том или ином виде, каждый сталкивался:
Но не многие подозревают о том, какая опасность тут скрывается.
Рассмотрим пример.
Вася Пупкин взял массив, прошелся по нему, увеличив на два все элементы:
Посмотрел дамп, увидел что задача решена, и ушел довольный:
Спустя некоторое время, Петрович решил дополнить этот участок кода другим перебором, дописав ниже:
Посмотрел, что его задача тоже решена, и с чувством выполненного долга закрыл файл:
Спустя какое-то время, стали вылезать необъяснимые баги. Почему?
Сделаем в конце кода 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. И все они когда-то обсуждались. Однако, этот случай, судя по моему опыту, так ра��пространён на практике, что заслуживает отдельного внимания.
