Благодаря тому, что РНР до сих пор остается очень легким для освоения, в эту отрасль приходит все больше новых людей, которые, вполне возможно, не имеют практики программирования на других языках. Вследствие того же фактора, можно достаточно умело оперировать этим языком и получать законченные программные продукты, например «сайты-визитки». И, в отличии от многих, я считаю, что это здорово. Но рано или поздно, если человек развивается в данном направлении, ему приходится вникать в более тонкие моменты программирования. И сегодня мы поговорим о ссылках в языке программирования PHP.
В PHP существует 3 вида ссылок:
1) Жесткие ссылки
2) Символические ссылки
3) Ссылки на объекты
Для чего они нужны и чем могут быть нам полезны? Давайте рассмотрим каждый тип ссылки подробно.
Жесткие ссылки. Жесткая ссылка представляет собой простой синоним переменной. Давайте посмотрим на следующий пример:
- Код: выделить все
/* ОБЫЧНОЕ ПРИСВОЕНИЕ */
$x = 10; // Есть переменная $х
$y = $x; // Есть переменная $y, которая представляем собой копию $х
echo "x = {$x}, y = {$y}\n"; // Проверяем результат
// x = 10, y = 10
$y = 50;
echo "x = {$x}, y = {$y}\n"; // Проверяем результат
// x = 10, y = 50
unset($x, $y); // Уничтожаем $x, $y
/* ПРИСВОЕНИЕ ПО ЖЕСТКОЙ ССЫЛКЕ */
$x = 10; // Есть переменная $х
$y = &$x; // Есть переменная $y, которая представляем собой жесткую ссылку на $х
$y = 50; // Эквивалентно записи $x = 50;
echo "x = {$x}, y = {$y}\n"; // Проверяем результат
// x = 50, y = 50
unset($y); // Если мы уничтожим ссылку, то это не приведет к удалению переменной на которую она ссылается
echo "x = {$x}, y = {$y}\n"; // Проверяем результат
$y = &$x; // Опять создаем жесткую ссылку;
unset($x); // Уничтожаем $x
echo "x = {$x}, y = {$y}\n"; // Проверяем результат
echo "\n\n\n\n";
То есть для того, чтобы создать переменную-ссылку на другую переменную нужно при присвоении перед правым операндом поставить знак передачи по ссылке – «&»(стр. 16). На первый взгляд эта операция кажется абсолютно бесполезной, но это не так. Дальше мы в этом убедимся.
И обязательно запомните: жесткая ссылка может быть уничтожена только оператором unset($жесткая_ссылка). Во всех остальных случаях она будет «преследовать» переменную, на которую ссылается, и Вас по всей своей области видимости. Если мы удалим переменную, на которую ссылается, какая-то ссылка, то переменная удалится из памяти, а ее значение в памяти будет все так же закреплено за ссылкой. Или мы удаляем ссылку оператором unset(), но это не влияет на переменную, на которую она ссылалась. Это часто приводит к ошибкам, которые непросто отловить.
Кстати, для тех, кто знаком с языком С и архитектурой памяти, можно добавить, что несмотря на то, что в PHP есть подобная возможность, в нем невозможно узнать адрес памяти, в котором храниться переменная, и невозможно ссылаться на этот адрес.
Символические ссылки. Символическая ссылка – это простая строковая переменная, в которой храниться имя другой переменной. Для того чтобы получить значение такой ссылки нужно применить оператор разыменования – знак «$» перед именем ссылки. Давайте посмотрим пример:
- Код: выделить все
/* ПРИМЕНЕНИЕ СИМВОЛИЧЕСКОЙ ССЫЛКИ */
$x = 1; // Инициализируем переменную x
$y = 2; // Инициализируем переменную y
$linkX = 'x'; // Это переменная будет использоваться как символическая ссылка на x
$linkY = 'y'; // Это переменная будет использоваться как символическая ссылка на y
echo "x = {$$linkX}, y = {$$linkY}\n"; // Вот так применяются символические ссылки
// x = 2, x = 2
unset($x, $y); // Уничтожаем $x, $y
Тоже не самая полезная фича, но если стоит такая задача, без нее никак:
/* ПРАКТИЧЕСКОЕ ПРИМЕНЕНИЕ СИМВОЛИЧЕСКОЙ ССЫЛКИ */
// Есть 2 класса.
class Dynamic_Library_First {};
class Dynamic_Library_Second {};
// Есть какой-то динамический во времени параметр.
// В зависимости от этого параметра нам нужно создать тот или иной класс.
$pseudoRandom = rand(0, 1);
// Мы должны вычислить его имя
$className = 'Dynamic_Library_';
if ($pseudoRandom == 0) {
$className .= 'First';
} else if ($pseudoRandom == 1) {
$className .= 'Second';
} else {
echo "Something went wrong with pseudo - random function\n";
}
// И выполнить следующее действие. Конструктор, кстати, работает как всегда.
$object = new $className();
// Вуаля!
if ($object instanceof Dynamic_Library_First) {
echo 'Object is instance of Dynamic_Library_First';
} else if ($object instanceof Dynamic_Library_Second) {
echo 'Object is instance of Dynamic_Library_Second';
} else {
echo 'Something went wrong';
}
echo "\n";
// Запустите этот пример несколько раз что бы увидеть его действие
Ссылки на объекты. Основной фичей РНР5 считают нововведения в объектно-ориентированном программировании. Их много и речь сейчас не об этом. Речь о том, что для удобства работы оператор присвоения «=» работает по другому, когда речь идет о объектах класса. Посмотрите следующий пример:
- Код: выделить все
/* ССЫКИ НА ОБЪЕКТЫ */
// Есть класс личность
class Person {
public $weight;
}
// Я - личность
$me = new Person();
// и вешу 80 кг
$me->weight = 80;
// Это моя копия для экспериментов над внешностью :)
$copyOfMe = $me;
// Она весит больше, чем я - 100 кг
$copyOfMe->weight = 100;
// Смотрим, что получилось
echo "My weight = {$me->weight}, My copy weight = {$copyOfMe->weight}\n";
// В РНР4 получилось: My weight = 80, My copy weight = 100
// В РНР5 получилось: My weight = 100, My copy weight = 100
При операциях присвоения с объектами РНР4 создавал их копии, по аналогии с операциями присвоения переменных. РНР5 в таких случаях создает жесткую ссылку(см. выше), присваивает не копию объекта, а ссылку на адрес в памяти.
Для того чтобы этот пример заработал в PHP5 правильно и мы могли бы проводить эксперименты над моей внешностью, нужно его незначительно изменить:
- Код: выделить все
// Есть класс личность
// ...
// Я - личность
$me = new Person();
// и вешу 80 кг
$me->weight = 80;
// Это моя копия для экспериментов над внешностью :)
$copyOfMe = clone $me;
// Она весит больше, чем я - 100 кг
$copyOfMe->weight = 100;
// Смотрим, что получилось
echo "My weight = {$me->weight}, My copy weight = {$copyOfMe->weight}\n";
// В РНР4 получилось: ошибка
// В РНР5 получилось: My weight = 80, My copy weight = 100
Теперь в РНР5 все будет работать правильно, а в РНР4 будет ошибка так как там отсутствует оператор «clone».
Вот мы и познакомились со ссылками в РНР, а так же рассмотрели некоторые примеры их применения. Но на самом деле есть еще пару интересных фокусов со ссылками:
1) Передача элемента массива по ссылке в оператор «foreach»
2) Передача аргумента функции по ссылке
Передача элемента массива по ссылке в оператор «foreach». В подавляющем большинстве случаев для перебора элементов массива РНР программисты используют оператор «foreach». Это вполне оправдано, так как с ним действительно удобно работать:
- Код: выделить все
/* РАБОТА С ОПЕРАТОРОМ foreach */
$array = array(1, 2, 3, 4, 5);
foreach ($array as $a) {
echo "{$a}<br>";
}
А что в это время происходит внутри интерпретатора? Интерпретатор создает копию массива $array и итерирует по ней. Иногда это полезно, иногда нет. Например, мы хотим умножить все элементы массива на 2:
- Код: выделить все
/* УМНОЖЕНИЕ ЭЛЕМЕНТОВ МАССИВА НА 2 ИСПОЛЬЗУЯ ОПЕРАТОР foreach */
$array = array(1, 2, 3, 4, 5); // Определяем массив значений
foreach ($array as $a) {
$a *= 2; // Умножаем элемент массива на 2
}
// Функция implode() "склеивает" массив в строку используя какую-то
// строку как разделитель
echo implode('<br>', $array); // Выводим каждый элемент массива с новой строки
Этот пример выведет нам «1<br>2 <br>3<br> 4<br> 5». Как видите – ничего не умножилось. При этом синтаксически код работает правильно. Что же мы тогда умножали на 2? А на 2 мы умножали ту самую копию массива, которую создал «foreach», и которая была успешно уничтожена, когда закончилась ее область видимости(тело оператора «foreach»). В РНР5 появилась замечательная возможность «сказать» оператору «foreach», чтобы он не создавал копию массива, а создал только жесткие ссылки(см. выше) на элементы переданного ему массива. В этом методе есть некоторые плюсы:
А) Повышаем быстродействие – не выполняется копирование массива
Б) Уменьшая ресурсоемкость – не занимаем память сервера временным массивом
В) Можем изменить значение элемента массива прямо в цикле
Смотрим пример:
- Код: выделить все
/* УМНОЖЕНИЕ ЭЛЕМЕНТОВ МАССИВА НА 2 ИСПОЛЬЗУЯ ОПЕРАТОР foreach */
$array = array(1, 2, 3, 4, 5); // Определяем массив значений
foreach ($array as &$a) {
$a *= 2; // Умножаем элемент массива на 2
}
echo implode('<br>', $array); // Выводим каждый элемент массива с новой строки
Другими словами, если Вам не нужно создавать копию массива, а просто необходимо выполнить с ним какие-то вычисления, лучше всегда запускать «foreach» таким образом(см. листинг выше). Но тут есть одно предостережение: после окончания работы оператора «foreach», который работал со ссылками, ссылка на последний элемент массива не уничтожается в теле оператора и может быть случайно использована в последующем коде, что может привести к логической ошибке. Смотрите пример:
- Код: выделить все
/* УМНОЖЕНИЕ ЭЛЕМЕНТОВ МАССИВА НА 2 ИСПОЛЬЗУЯ ОПЕРАТОР foreach */
$array = array(1, 2, 3, 4, 5); // Определяем массив значений
foreach ($array as &$a) {
$a *= 2; // Умножаем элемент массива на 2
}
$a = 'abra cadabra'; // Сейчас последний элемент массива равен 'abra cadabra'
echo implode('<br>', $array); // Выводим каждый элемент массива с новой строки
Передача аргумента функции по ссылке. Первоначально я хотел описать эту тему более подробно, но она очень тесно пересекается с другой темой – функции, о которых я напишу в следующий раз. В функции можно передавать аргументы по жесткой ссылке(см. выше), иногда это бывает просто необходимо. Давай посмотрим пример:
- Код: выделить все
// Есть функция с одним аргументом, когда она будет запущена
// интерпретатор копирует значение аргумента.
function nonRef($a)
{
$a .= 'aaa';
return $a;
}
$a = 'bbb';
$a = nonRef($a);
echo "{$a}<br>";
// Есть другая функция с одним аргументом, который передается в нее по ссылке.
// Теперь функция ничего не будет копировать, а будет работать с областью памяти,
// в которой располагается переданный аргумент.
function ref(&$a)
{
$a .= 'aaa';
}
$a = 'bbb';
ref($a);
echo "{$a}<br>";
// Как видите результат работы этих 2 участков кода будет одинаков.
// Следующая строка кода вызовет ошибку, нельзя передавать ссылку на константу.
// ref('bbb');
// Тем не менее аргумент передаваемый по ссылке может иметь дефолтное значение
function refDef($a, &$b = 'bbb')
{
$a .= 'aaa';
$b .= 'aaa';
echo "{$a} --- {$b}<br>";
}
refDef('bbb');
// Кроме этого функции могут возвращать ссылки, для этого нужно
// поставить оператор передачи по ссылке "&" перед именем функции
// при ее определении и при каждом последующем вызове.
function &getRef(&$p)
{
return $p;
}
$a = 1;
$c = &getRef($a); // Равносильно $c = &$a;
$c = 22;
echo "a = {$a}";
Код достаточно хорошо документирован, более детально работу с функциями опишу в следующих статьях.
Я надеюсь, что этот материал поможет Вам разобраться в данной теме. Если Вам не понятен какой-то момент, то посоветую на основе приведенных листингов попробовать написать что-то самостоятельно.













