Страницы

Sunday 5 June 2011

Ссылки в PHP и foreach

Перевод записи из блога Johannes Schlüter http://schlueters.de/blog/archives/141-References-and-foreach.html

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

Какой результат выполнения этого кода:
<?php
$a = array('a', 'b', 'c', 'd');

foreach ($a as &$v) { }
foreach ($a as $v) { }

print_r($a);
?>
Мы дважды проводим итерацию по массиву, ничего не делая. Так что в результате никаких изменений не должно быть. Правильно? - Неправильно! Актуальный результат выглядит следующим образом:

Array
(
    [0] => a
    [1] => b
    [2] => c
    [3] => c
)

Для понимания, почему это происходит давайте вернемся на шаг назад и посмотрим на то, как реализованы переменные в PHP и что такое ссылки:

Переменная в PHP в основном состоит из двух вещей: "label" (метка) и "container" (контейнер). Метка записана в хэш-таблицу (В движке есть несколько оптимизаций, поэтому она не всегда может находится в хэш-таблице), которая может представлять таблицу символов функции, массива или таблицу свойств объектов. Таким образом, мы имеем имя и указатель на контейнер. Контейнер, внутренне называется "zval", сохраняет значение и некоторую мета информацию, также этот контейнер может быть новой хэш-таблицей с набором меток, указывающих на другие контейнеры, если мы сейчас создадим ссылку это послужит причиной создания ещё одной метки, которая будет указывать на тот же контейнер, что и первая метка. С этих пор обе метки имеют те же права в контейнере.

Теперь давайте посмотрим на ситуацию описанную выше. На изображении она выглядит следующим образом:



У нас есть шесть контейнеров (глобальная таблица символов на вершине, контейнер, содержащий массив под названием $a слева и один контейнер для каждого элемента справа). Теперь мы начинаем итерацию. Глобальна таблица символов получает новую запись для $v и v сделана ссылкой на контейнер первого элемента массива.

При изменении одного из $a[0] или $v, указывающих на один контейнер, следовательно, оказывается влияние и на другого. При продолжении итерации ссылка разрушается, и $v делается ссылкой на другие элементы. Таким образом, после окончания итерации $v является ссылкой на последний элемент.


Запомните: $v будучи ссылкой означает, что любые изменения $v затрагивает другие ссылки, в этой ситуации $a[3]. До сих пор не произошло ничего особенного, но теперь начинается вторая итерация. Она присваивает значение текущего элемента переменной $v для каждого шага. Теперь $v является ссылкой на тот же элемент, что и $a[3], поэтому путем присвоения значения $v, $a[3] тоже изменится:


Это продолжается также в течении следующих шагов.




И теперь мы можем легко догадаться, что произойдет на последнем шагу: $v присваивается значение последнего элемента, $a[3], а по скольку $a[3] является ссылкой на $v происходит присвоение самому себе самого себя, так что в действительности ничего не происходит.


И это результат мы видели выше.

Подведем итоги из этой истории из картинок: Будьте осторожны с ссылками! Они могут иметь очень странные эффекты.

Автор перевода: reket.

От переводчика:

Чтобы избежать подобных казусов, всегда удаляйте $v после цикла foreach
foreach ($a as &$v) {
//
}
unset($v);
Ссылки в PHP очень мощный инструмент, но не стоит использовать их везде и по всюду. Прежде чем их использовать, убедитесь, что код не может быть написан без ссылок или становится слишком запутанным без них. При правильном понимании работы ссылок, приведенный выше пример становится очевидным, но затрудняется понимание кода самим программистом и как результат требуется больше времени, чтобы разобраться как работает система.

4 comments:

  1. Привет, Reket! Спасибо за статью, интересно было почитать! Если есть возможность, напиши статью о способах создания Excel и других документов с помощью PHP на линуксе. Там есть подводные камни как я читал, с отображением русского языка в документах. На windows работая с COM объектами это легко решается. А вот на линуксе c PEAR нужно работать и у многих людей возникают трудности.

    ReplyDelete
  2. Спасибо, все понятно, четко и по делу!

    ReplyDelete
  3. Если честно, то автор написал какую-то хрень непонятную.

    ReplyDelete