Особенности использования переменных в php

В php область видимости переменной распространяется за границы блока, в котором переменная была инициализирована, ограничивается она только лишь контекстом метода класса или функции в котором находится. Так, например, переменная инициализированная в цикле for будет доступна так же за его пределами далее по коду. Такое поведение не свойственно, к примеру, в языках с, c++, в которых область видимости ограничена блоком, в котором переменная была создана. Поэтому иногда программисты, особенно пришедшие в php из рядов c-программистов, даже не догадываются о подобном поведении, вплоть до того момента, пока не начинают проявляться странные баги и глюки. Особо часто баги возникают при работе со ссылочными переменными.

Рассмотрим такой пример:

<?php
 
//1
 
$arr = range(1,3);
  foreach(
$arr as &$item )
  {
   
$item++;
  }
 
print_r( $arr );
 
//2
 
$arr2 = range(101, 103);
  foreach(
$arr2 as $key => $item )
  {
   
$arr2[$key]++;
  }
 
print_r( $arr2 );
 
print_r( $arr );
?>

В первом цикле использована ссылочная переменная $item. Результат выполнения вполне ожидаем - каждое значение массива увеличится на единицу.

В результате выполнения второго цикла мы так же видим то, что и ожидаем - каждый элемент массива $arr2 был инкрементирован. Но если теперь проверить содержимое массива $arr, который был заполнен в первой половине примера, мы увидим то, чего явно не ожидали. Последний элемент массива $arr стал равен значению последнего элемента массива $arr2 до преобразования, т.е. 103. Разберемся в причинах подобного поведения.

После выполнения первого цикла, переменная $item остается объявленной как ссылка на последний элемент массива $arr. Следовательно, в начале каждой итерации цикла, когда zend engine присваивает переменной $item значение следующего элемента массива $arr2, на самом деле это значение присваивается последнему элементу массива $arr. Таким образом, после выполнения последней итерации, значение последнего элемента массива $arr равняется значению последнего элемента массива $arr2 до преобразования.

Если взять на вооружение простые рекомендации, подобных ситуаций можно избежать:
1) Никогда не стоит доверять своим старым, а тем более чужим переменным, т.е. тем, которые выше по коду уже использовались в других целях. Даже если сейчас все отлично, ваш коллега со временем может модифицировать код, или даже это можете сделать вы - в результате получите нежеланный, явно не понятный на первый взгляд эффект.
2) Если все-таки понадобится использовать старую переменную, то необходимо ее перед использованием деинициализировать функцией unset().
3) Приучите себя до и после использования всегда следить за своими и тем более за чужими переменными и очищать память от более не нужных данных.

Но если вдруг что-то все же произошло, как же быстро локализовать место возникновения ошибки? В этом нам поможет функция var_dump(). Посмотрим, например, при помощи var_dump() на содержание массива $arr после выполнения второго цикла.

array
0 => int 2
1 => int 3
2 => &int 103

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

Рекомендация 4 - для дебага скриптов предпочтительнее использовать функцию var_dump() вместо print_r().

Учитесь на чужих ошибках, удачи.

Теги: