В предыдущем выпуске я вкратце упомянул о references и обещал, что мы поговорим о них подробнее в одном из следующих выпусков. Сегодняшний выпуск мы посвятим именно рассмотрению этого вопроса, т.к. references играют важную роль в PHP4 и используются очень часто при решении различных задач позволяя сделать это решение более легким, гибким и элегантным.
References в PHP
References появились в PHP начиная с четвертой версии, вследствие того, что теперь в качестве ядра PHP используется Zend scripting engine. Для того, чтобы вам стала понятна суть references - необходимо объяснить как Zend engine работает с переменными.
Внутри кода Zend engine существует разделение между именем переменной и ее значением. Это можно представить как две различные таблицы:
Обычно, когда в программе создаются переменные, между элементами в этих двух таблицах создается обычная связь "1:1" (один к одному):
Так вот, суть механизма references состоит в том, что они позволяет вам изменять связи между элементами этих таблиц. При этом references отличаются от указателей, имеющихся в C/C++ и других языках. Отличие состоит в том, что указатель является отдельной переменной, содержащей указатель, на местоположение в памяти другой переменной. В PHP же механизм references позволит вам создать несколько имен для одной и той же переменной! Важно понять это, чтобы правильно использовать references в своем коде.
Лучше всего рассмотреть то, как это работает на примере:
$b = &$a; // Знак & перед именем переменной указывает на использование reference
Посмотрим, как в этом случае будут выглядеть внутренние связи между именами переменных и их значениями:
Как видите, имя переменной $b теперь указывает на значение переменной $a. Заметьте, что значение самой переменной $b теперь не имеет ни одного имени, связанного с ней и поэтому больше недоступно из PHP кода. Ядро PHP автоматически уничтожает такие переменные, поэтому значение 3, ранее бывшее значением переменной $b, перестает существовать с этого момента.
Поскольку теперь значение первой переменной в таблице значений имеет два имени ($a и $b), то обращение к любому из этих имен будет рассматриваться как обращение к этой переменной:
Также, как и обращение к значению переменной, присваивание значения любому из этих имен вызовет присваивание значения одной и той же реальной переменной:
$a = 1;
$b = 4;
Более того, даже удаления имени переменной, которая изначально была связана с этим значением ничего не изменит, значение переменной будет существовать до тех пор, пока есть хоть одно имя переменной, связанное с ней:
References могут использоваться не только для переменных. Например в предыдущем выпуске было показано, как использовать references для передачи параметров в функцию. Как вы наверняка знаете - в программировании существует два основных метода передачи параметров в процедуры или функции:
передача "по значению" (passing by value) - В процедуру (функцию) передается значение, являющееся копией оригинальной переменной. Все изменения этого значения внутри процедуры (функции) никак не сказываются на оригинальной переменной.
передача "по ссылке" (passing by reference) - В процедуру передается ссылка на оригинальную переменную. Все изменения этого значения внутри процедуры (функции) приводят к соответствующим изменениям оригинальной переменной.
Если ранее (до четвертой версии) в PHP можно было использовать только первый метод передачи параметров, то теперь вам доступны оба метода. Посмотрим, как это выглядит, на примере:
// Глобальная переменная, содержащая некоторое значение $value = 1; // Функция, инкрементирующая значение преданной ей в качестве параметра переменной function counter(&$var) { $var++; }; // Вызываем функцию, передавая ей в качестве параметра имеющуюся у нас переменную counter($value); // Смотрим, как изменилось значение переменной print $value; // Результат: 2
Как видно из этого примера - функция принимает параметр не "по значению", а "по ссылке" (на это указывает наличие символа & перед именем переменной в разделе описания аргументов функции), что приводит к изменению значения переданной в функцию глобальной переменной. Кстати, заметьте, что при обращении к этой переменной внутри самой функции знак & не используется. Это возможно, т.к. способ передачи параметров однозначно определен при описании функции.
Конечно, такого же результата можно было бы добиться и другим способом:
Но этот способ не является примером хорошего кода, т.к. здесь имеется явная неоднозначность - как именно будет передан параметр в функцию (по значению или по ссылке), что может привести к ошибкам в коде. Кроме того, последние версии PHP будут предупреждать вас о том, что такой синтаксис является deprecated (осуждаемым) и может не поддерживаться следующими версиями языка.
References можно использовать не только для того, чтобы передавать параметры в функции, но и для того, чтобы получать результат. Для этого необходимо поставить знак & перед именем функции:
function &getValue()
В качестве примера того, когда это бывает полезно, можно привести функцию, которая осуществляет поиск в каком-либо массиве со сложной структурой и возвращает reference на найденный элемент массива.
Но особенно часто references используются при работе с объектами. И это вполне логично, ведь при работе с объектами практически всегда необходимо оперировать именно с самим экземпляром объекта, а не с его копией, создаваемой каждый раз при присваивании или передаче в функцию "по значению".
Использование references зачастую начинается в тот момент, когда создается экземпляр объекта:
$newObj = &new myObject();
Приведенный выше код создает экземпляр объекта myObject() и передает reference на него в переменную $newObj. В этом случае все будет работать нормально. А теперь посмотрим на другой вариант того же кода:
$newObj = new myObject();
Единственное отличие состоит в том, что в переменной $newObj сохраняется не только что созданный экземпляр объекта myObject(), а копия этого экземпляра. При этом оригинал автоматически уничтожается по причинам описанным выше - не будет ни одного имени переменной, связанного с ним.
На первый взгляд разница между двумя этими путями невелика - все равно в результате мы получаем экземпляр необходимого нам объекта. Однако разница есть и она таится в конструкторе объекта. Все дело в том, что конструктор объекта вызывается до того, как завершится работа оператора new. Поэтому, если в конструкторе объекта вы создали какие-либо references в переменных объекта, используя для этого переменную $this, то все эти references будут потеряны! Так что вам необходимо четко следить за тем, как именно вы создаете экземпляры ваших объектов, чтобы избежать подобных, далеко не очевидных, проблем.
Еще один момент, когда использование references просто необходимо - создание связей между объектами и объединение объектов в структуры. Здесь вам вообще не удастся обойтись без помощи references, т.к. здесь необходимо иметь в переменных объекта ссылку именно на экземпляр объекта.
В качестве примера структуры, состоящей из объектов можно привести дерево объектов, каждым листом которого является отдельный объект. В такой структуре references используются как минимум в двух местах:
Создание связи с объектом-предком
Хранение массива объектов-наследников
И, напоследок, еще один пример кода, который может понадобиться вам довольно часто, но также может привести к ошибкам, которые будет достаточно сложно обнаружить вследствие их неочевидности. Это пример обработки массива объектов, в котором каждый элемент является reference на объект. Для начала код создания подобного массива. Я использовал очень простой объект просто для того, чтобы проиллюстрировать дальнейший код:
// В этом массиве будут храниться объекты. // Каждый элемент массива - reference на соответствующий объект $data = array(); // Очень простой класс class myObject { var $id; function setID($id) { $this->id = $id; } }; // Заполняем массив объектами. for ($i=0;$i<10;$i++) $data[] = &new myObject();
Теперь нам, к примеру, необходимо задать идентификатор для каждого объекта в массиве. Первое, что приходит в голову -простой перебор элементов массива и присвоение идентификатора каждому из них:
$id = 1; foreach($data as $obj) { $obj->setID($id++); };
Этот код, разумеется не будет работать, потому что здесь references просто не используются и работа ведется с копиями объектов, хранящихся в массиве. Правильным кодом для этого будет следующее:
$id = 0; // Здесь мы перебираем не сами элементы массива, // а ключи этого массива с тем, чтобы сохранить references foreach(array_keys($data) as $key) { // Получаем reference на текущий объект $obj = &$data[$key]; // Выполняем необходимые действия с ним $obj->setID($id++); // Уничтожаем созданный reference. // Если этого не сделать, то на следующем шаге // мы просто уничтожим объект, с которым работали в прошлый раз // Почему? См. выше о том, как работают references unset($obj); };
Заключение
Как вы могли убедиться, references - это очень мощный механизм. И иногда без них просто не обойтись. Однако как и каждым мощным средством пользоваться ими надо аккуратно, чтобы избежать множества ошибок.