PHP: полный мануал с примерами, функциями и ООП
Практический мануал PHP: от переменных и читаемого кода до безопасных форм, интеграций, базы данных и ООП на примере магазина. Учебный маршрут, поиск функций, подробные примеры, разбор ошибок и исходники для скачивания.

Дерево руководства
Глава 18 · После функций и массивов
Ссылки: когда два имени изменяют одно значение
От двух имён одного значения до передачи и возврата ссылок, перепривязки, unset, общей структуры массивов и диагностики ReflectionReference.
Два ярлыка у одного значения: что такое ссылка PHP
В учебном примере остаток называют stock, а в другом месте — available. Иногда нужно, чтобы изменение через любое имя было видно через другое. Ссылка PHP связывает имена с общим значением. Аналогия — два ярлыка на одном ящике: переименованием ярлыка не создаётся второй ящик. Это модель поведения переменных, а не схема физических адресов памяти.
$stock = 12;— создаём значение$available =& $stock;— связываем второе имя$available = 10;— изменяем общее значениеecho $stock;— видим 10 через первое имя
Знак $ начинает имя переменной. Обычный = присваивает значение. В записи =& амперсанд задаёт присваивание по ссылке; пробел между = и & допустим. Echo выводит значение; точка с запятой заканчивает инструкцию.
<?php
$stock = 12;
$available =& $stock;
$available = 10;
echo $stock;
Ожидаемый результат
10available не хранит независимую копию числа: изменение видно через stock. Здесь мы изучаем механизм; для складской системы с одновременными заказами одних переменных в памяти процесса недостаточно.
<?php
$stock = 12;
$available = $stock;
$available = 10;
echo $stock;
Ожидаемый результат
12Без & изменение второго числа не меняет первое. PHP может использовать внутренние оптимизации памяти, но они не отменяют это наблюдаемое различие. Не добавляйте & просто ради предположения об экономии памяти.
Изменить значение и перепривязать имя — разные действия
Обычное присваивание через любое имя меняет общее значение. Новое присваивание по ссылке перепривязывает только имя слева. Оно не перемещает весь прежний набор связанных имён к новому значению. PHP-ссылка не является указателем C: у неё нет доступной арифметики адресов.
<?php
$firstStock = 12;
$secondStock = 20;
$current =& $firstStock;
$current =& $secondStock;
$current = 21;
echo $firstStock, '/', $secondStock;
Ожидаемый результат
12/21current теперь связан со secondStock; firstStock остался 12. Символ / в выводе — обычный разделитель текста. Поэтому функция, перепривязавшая свой локальный параметр, не обязательно перепривяжет переменную вызывающего кода.
<?php
$count = 3;
$alias =& $count;
$snapshot = $alias;
$alias = 4;
echo $count, '/', $snapshot;
Ожидаемый результат
4/3snapshot получил обычное число, а не ещё один ярлык. Чтобы присоединить третий ярлык, потребовалось бы $snapshot =& $alias. У массивов с внутренними ссылками и объектов есть дополнительные правила, которые ниже разобраны отдельно.
unset снимает имя, присваивание null меняет значение
unset($alias) убирает привязку этого имени. Другие имена сохраняют своё значение. Присваивание $alias = null оставляет связь, но записывает null в общее значение. Эти действия решают разные задачи.
<?php
$count = 3;
$alias =& $count;
unset($alias);
$alias = 9;
echo $count, '/', $alias;
Ожидаемый результат
3/9После unset новое присваивание создаёт отдельную переменную. Так разрывают временную связь после foreach по ссылке. Это не удаление товара из массива: для него нужно unset($items[$key]).
<?php
$count = 3;
$alias =& $count;
$alias = null;
var_dump($count);
Ожидаемый результат
NULL
var_dump показывает тип и значение. Оба имени всё ещё связаны; null видно через count. isset не отличает такую переменную от отсутствующей: он проверяет наличие ненулевого значения.
Ссылка на отсутствующий элемент может сама создать его
Обычное чтение неизвестного ключа массива и получение ссылки — разные действия. Ссылке требуется существующее место хранения, поэтому привязка к отсутствующей переменной или ключу может создать значение null. Проверяющая функция не должна случайно менять вход только потому, что параметр объявили с &.
<?php
$order = [];
$comment =& $order['comment'];
if (array_key_exists('comment', $order)) {
echo 'Поле уже существует: ', $order['comment'] ?? 'ещё не заполнено', "\n";
}
$comment = 'Позвонить заранее';
echo 'Памятка сборщику: ', $order['comment'], "\n";
Ожидаемый результат
Поле уже существует: ещё не заполнено
Памятка сборщику: Позвонить заранее
Связь с отсутствующим comment создала этот ключ со значением null. Array_key_exists возвращает true и разрешает первую подпись; ?? подставляет текст для null. Запись через comment затем меняет поле самого order, откуда берётся памятка. Обычное чтение order[comment] ?? null не создало бы ключ. Поэтому параметр по ссылке нельзя бездумно добавлять к функции, которая должна лишь проверить наличие данных.
Проверьте себя. Можно ли обернуть чтение в function read(&$value), чтобы просто посмотреть отсутствующий ключ?
Разбор
Такой параметр передаётся по ссылке и может создать ключ. Если функция должна только читать, принимайте значение без & и отдельно определите поведение при отсутствии.
Почему функция может изменить вашу переменную
После вызова функции остаток на складе неожиданно изменился, хотя вы ничего не присваивали $stock. Один из возможных ответов — параметр принят по ссылке. Такая функция намеренно работает с переменной вызывающего кода. Прежде чем применять это к программе, увидим связь на двух именах.
Результат: вы прочитаете запись с &, поймёте её побочный эффект и сможете убрать случайную связь после foreach. Нужны переменные, параметры функций и массивы.
Каждый пример запускается отдельно. Скачайте его архив, распакуйте целиком, откройте терминал в этом каталоге и выполните php example.php. Сравните вывод с блоком «Ожидаемый результат». Не объединяйте примеры в один файл: некоторые намеренно повторяют имена функций и классов.
Три места, где здесь встречается &
$available =& $stock;
function reserveUnits(int &$available, int $quantity): void { /* ... */ }
foreach ($items as &$value) { /* ... */ }
unset($value);| Запись | Разбор |
|---|---|
| $available =& $stock | Имена available и stock обращаются к одному значению. Это не копия текущего числа. |
| int &$available | Параметр ожидает переменную типа int и может изменить её у вызывающего кода. |
| as &$value | На каждой итерации value связывается с текущим элементом массива. |
| unset($value) | Удалить это имя/связь. Остальные имена и соответствующий элемент массива сохраняются. |
Ссылка PHP — связь имён, а не адрес памяти с арифметикой указателей. Синтаксис & также бывает оператором побитового И между числами; смысл определяется местом записи. Здесь нас интересует именно связывание переменных.
Минимальный опыт: 12 стало 10
Два имени обращаются к одному значению
После $available =& $stock меняем доступное количество через available. Через stock видим то же изменение.
- Общее значение: 12 → 10
Один остаток уменьшили на две единицы.
- $stock
Чтение этого имени теперь даёт 10.
- $available
Второе имя связано с тем же значением через =&.
unset($available) снимает одно имя. Значение $stock остаётся 10; это не удаление данных склада.
<?php
declare(strict_types=1);
$stock = 12;
$available =& $stock;
$available -= 2;
echo 'Остаток: ', $stock, "\n";
unset($available);
$available = 99;
echo 'Старый остаток: ', $stock, "\n";
echo 'Новая переменная: ', $available, "\n";
Ожидаемый результат
Остаток: 10
Старый остаток: 10
Новая переменная: 99
=& связывает два имени с одним значением. Изменение через available поэтому видно через stock. unset убирает имя available из этой связи, а не обнуляет stock. Последующее присваивание создаёт отдельную переменную. Это демонстрация механики, не рекомендованная архитектура складского учёта.
Параметр по ссылке: право изменить переменную вызывающего кода
Обычный параметр получает значение. Запись function markSent(&$status) создаёт параметр, связанный с переданной переменной. В самом вызове & не пишут. Фигурные скобки ограничивают тело функции; изменение параметра видно после возврата. Сначала — один эффект без типизации и дополнительных проверок.
<?php
function markSent(&$status)
{
$status = 'sent';
}
$status = 'new';
markSent($status);
echo $status;
Ожидаемый результат
sentВызов не присваивал результат, но status изменился. Такое поведение должно быть очевидно из имени и документации функции. Другой вариант — переписать функцию на возврат нового значения через return и присвоить этот результат снаружи. Не присваивайте результат текущей markSent: она не возвращает значение и даст null.
Передают переменную или значение, которое сама функция возвращает по ссылке. Литерал и вычисленное выражение не дают подходящего изменяемого места. Ошибка ниже намеренно перехватывается через try/catch после знакомства с исключениями.
<?php
function addOne(int &$count): void
{
$count += 1;
}
try {
addOne(3);
} catch (Error $error) {
echo "Нужна переменная\n";
}
Ожидаемый результат
Нужна переменная
PHP не может связать параметр с литералом. Создайте $count = 3, затем вызовите addOne($count). Старый синтаксис вызова addOne(&$count) в современных версиях не применяется.
return и изменение по ссылке — два канала результата
У ряда функций значение возвращается через return, а дополнительный результат — через параметр с &. Например, сортировка изменяет массив и отдельно сообщает об успехе. Не присваивайте такой признак обратно вместо данных. В следующем примере sort сортирует изменяемый массив.
<?php
$weights = [300, 100, 200];
$status = sort($weights, SORT_NUMERIC);
if ($status) { echo "Массы упорядочены\n"; }
foreach ($weights as $position => $grams) {
echo 'Образец ', $position + 1, ': ', $grams, ' г', "\n";
}
Ожидаемый результат
Массы упорядочены
Образец 1: 100 г
Образец 2: 200 г
Образец 3: 300 г
Sort изменяет сам weights через ссылочный первый параметр и возвращает true в status. Проверка статуса разрешает подпись; foreach читает готовые массы из weights. Прежние ключи заменены 0/1/2, поэтому здесь это номера образцов в новом порядке, не постоянные ID. Присваивание weights = sort(weights) подменило бы массив значением true.
Не нужно создавать второй канал, чтобы обойти якобы невозможный возврат нескольких значений: функция может вернуть один массив или объект с несколькими полями. Ссылка оправдана, когда изменение входа является частью понятного контракта.
Ссылка в контракте функции
<?php
declare(strict_types=1);
function reserveUnits(int &$available, int $quantity): void
{
if ($quantity < 1 || $quantity > $available) {
throw new InvalidArgumentException('Недостаточный остаток или неверное количество');
}
$available -= $quantity;
}
$stock = 5;
reserveUnits($stock, 2);
echo 'После резерва: ', $stock, "\n";
try {
reserveUnits($stock, 20);
} catch (InvalidArgumentException) {
echo "Резерв отклонён\n";
}
echo 'После отказа: ', $stock, "\n";
Ожидаемый результат
После резерва: 3
Резерв отклонён
После отказа: 3
Знак & стоит в объявлении параметра. Вызов reserveUnits($stock, 2) передаёт переменную без дополнительного &: функция получает право изменить именно её. Проверка выполнена до изменения, поэтому отказ оставляет 3. Это память одного процесса; параллельные заказы требуют отдельной транзакции/протокола резервирования в хранилище.
Такой вызов меняет данные снаружи, поэтому имя reserveUnits и документация должны обещать этот эффект. Часто проще вернуть новый остаток: $stock = remainingStock($stock, 2). Присваивание тогда видно в месте вызова. Нельзя передавать литерал 5 или вычисленное выражение туда, где нужен изменяемый параметр. Не добавляйте & ради предположения о скорости: сначала выберите понятный договор и измерьте реальную задачу.
Перепривязка локального параметра не заменяет внешнее имя
Параметр — отдельное имя, которое при передаче по ссылке связывается с внешним значением. Если потом связать сам параметр с другой переменной, перепривязывается это локальное имя. Это показывает, почему PHP-ссылки нельзя объяснять как двойные указатели C.
<?php
function chooseOther(&$current, &$other): void
{
$current =& $other;
$current = 9;
}
$first = 3;
$second = 7;
chooseOther($first, $second);
echo $first, '/', $second;
Ожидаемый результат
3/9first осталось 3, second изменилось на 9. Если требуется заменить значение first значением second, внутри функции нужно обычное $current = $other, без перепривязки.
<?php
function forgetLocal(&$value): void
{
unset($value);
$value = 'local';
}
$status = 'new';
forgetLocal($status);
echo $status;
Ожидаемый результат
newУдалено локальное имя value. Новое присваивание относится к новой локальной переменной. Для удаления поля передайте сам массив и нужный ключ, затем выполните unset($array[$key]) с понятным контрактом.
Возврат ссылки: выбрать существующее место для изменения
Иногда функция ищет нужный элемент сложной структуры, а вызывающий код должен менять именно его. function &name() обещает возврат по ссылке. Получатель пишет $selected =& name();. Это отличается от передачи параметра: знак & нужен и в объявлении результата, и в присваивании получателя. В return возвращается переменная, а не выражение вроде $value + 1.
$editable =& draftNote();вызывает функцию и просит связь- Функция выбирает свою существующую переменную
$note return $note;возвращает связь в место вызова$editable = 'review';меняет выбранное значение- Следующий вызов читает
review
<?php
function &draftNote(): string
{
static $note = 'new';
return $note;
}
$editable =& draftNote();
$editable = 'review';
$copy = draftNote();
$copy = 'local';
echo draftNote(), '/', $copy;
Ожидаемый результат
review/localstatic сохраняет note между вызовами в пределах одного выполнения PHP. Обычные HTTP-запросы FPM начинают это состояние заново, даже если процесс повторно используется. У длительного CLI-worker одно выполнение может обрабатывать много заданий. editable связан с note, copy получил обычную строку. Для независимых заказов используйте явное хранилище или объект; этот static не является сохранением заметки между HTTP-запросами.
Теперь практический поиск по артикулу. Массив rows передаётся по ссылке, чтобы поиск выбрал элемент исходной корзины. array_key_exists проверяет наличие; исключение прекращает поиск неизвестного товара. Для новой архитектуры метод updateQuantity с проверкой границ обычно безопаснее, чем выдача прямой ссылки на внутреннюю структуру.
Сначала ниже будет выбрана одна существующая строка корзины. Затем вызывающий код изменит количество через полученную связь и пересчитает весь заказ. Так видно практическое назначение возврата ссылки, а не только символы &.
<?php
function &cartRow(array &$rows, string $sku): array {
if (!array_key_exists($sku, $rows)) {
throw new OutOfBoundsException('Нет такого артикула');
}
return $rows[$sku];
}
$cart = ['tea' => ['quantity' => 1, 'price' => 150], 'cup' => ['quantity' => 2, 'price' => 200]];
$row =& cartRow($cart, 'tea');
$row['quantity'] = 3;
unset($row);
$total = 0;
foreach ($cart as $sku => $line) {
$lineTotal = $line['quantity'] * $line['price'];
$total += $lineTotal;
echo $sku, ': ', $line['quantity'], ' × ', $line['price'], ' = ', $lineTotal, ' коп.', "\n";
}
echo 'К оплате: ', $total, ' коп.', "\n";
Ожидаемый результат
tea: 3 × 150 = 450 коп.
cup: 2 × 200 = 400 коп.
К оплате: 850 коп.
Первый & у параметра rows связывает его с исходной cart. Амперсанд перед именем функции обещает возврат ссылки на выбранную строку rows[tea]; =& у вызывающего сохраняет эту связь в row. Присваивание quantity=3 меняет строку cart, после чего unset снимает временное имя. Следующий foreach читает уже 3, считает 450+400=850 и показывает итог. Без =& получатель получил бы независимое значение массива. Форма/цены/количества заранее проверены; поиск по ссылке сам не делает валидацию или запись в базу.
<?php
function &attemptCounter(): int
{
static $count = 0;
return $count;
}
function recordAttempt(int &$count): void
{
$count += 1;
}
recordAttempt(attemptCounter());
echo attemptCounter();
Ожидаемый результат
1Здесь допустим результат вызова: attemptCounter явно возвращает по ссылке. Обычную функцию, возвращающую число по значению, подставлять вместо неё нельзя. В прикладном коде такой скрытый счётчик усложняет тесты; явная переменная или объект обычно понятнее.
Практическая ловушка: ссылка пережила цикл
<?php
declare(strict_types=1);
$broken = [2, 3, 4];
foreach ($broken as &$value) {
$value *= 2;
}
// Ошибка: $value всё ещё связан с последним элементом.
foreach ($broken as $value) {
// Даже пустое тело не отменяет присваивание в $value на каждой итерации.
}
echo 'Без unset: ', json_encode($broken, JSON_THROW_ON_ERROR), "\n";
unset($value);
$fixed = [2, 3, 4];
foreach ($fixed as &$value) {
$value *= 2;
}
unset($value);
foreach ($fixed as $value) {
}
echo 'С unset: ', json_encode($fixed, JSON_THROW_ON_ERROR), "\n";
Ожидаемый результат
Без unset: [4,6,6]
С unset: [4,6,8]
В первом цикле изменили элементы на месте. После него $value сохранил связь с последним элементом. Второй цикл присваивал этому же имени прочитанные значения и незаметно менял последний элемент. В исправленном варианте связь снята сразу после первого цикла. Альтернатива без ссылок: foreach ($items as $key => $value) { $items[$key] = $value * 2; }.
Блок foreach не создаёт отдельную область видимости переменной. Выполняйте unset сразу после цикла по ссылке, до повторного использования того же имени. Для построения нового списка обычный foreach с добавлением в $result часто лучше показывает, что исходные данные должны сохраниться.
Копия массива может сохранить ссылки внутри элементов
Обычное присваивание массива не означает безусловного глубокого копирования любой структуры. Если элемент уже участвует в общей ссылке, этот общий элемент может сохраниться в копии. Типичная история: взяли короткий alias для вложенного блока API-ответа, затем сохранили «снимок», а редактирование затронуло оба.
<?php
$order = ['quantity' => 2, 'label' => 'first'];
$quantity =& $order['quantity'];
$snapshot = $order;
$snapshot['quantity'] = 5;
$snapshot['label'] = 'copy';
echo $order['quantity'], '/', $order['label'], "\n";
echo $snapshot['quantity'], '/', $snapshot['label'];
Ожидаемый результат
5/first
5/copyquantity оказался общим, label остался независимым. Ошибка не случайна и не зависит от похожести имён. Нужно понимать, какие элементы уже были ссылками к моменту копирования.
<?php
$order = ['quantity' => 2];
$quantity =& $order['quantity'];
$quantity = 3;
unset($quantity);
$snapshot = $order;
$snapshot['quantity'] = 5;
echo $order['quantity'], '/', $snapshot['quantity'];
Ожидаемый результат
3/5В этой структуре удалён единственный дополнительный alias до копирования. Это не универсальная функция разрыва всех вложенных связей: если другие ссылки остаются, контракт другой. Не используйте JSON-круговорот как универсальное клонирование: он меняет типы, теряет возможности объектов и не поддерживает циклы.
Ссылки также могут образовывать самоссылающиеся массивы. Рекурсивный обход или копирование без контроля структуры тогда не завершится корректно. Для внешнего JSON такая ссылка сама не появляется: JSON содержит данные дерева, а связь создал PHP-код. При преобразовании строите новый массив по проверенной схеме, а не обходите произвольный граф без границ.
global и use (&$value): другие места появления связи
global $name внутри функции связывает локальное имя с одноимённой глобальной переменной; это не глобальность всех переменных функции. Перепривязка локального имени подчиняется уже разобранным правилам. Для замыкания use (&$name) создаёт явную связь с переменной внешней области.
<?php
$mode = 'web';
function localMode(): void
{
global $mode;
$temporary = 'cli';
$mode =& $temporary;
$mode = 'worker';
}
localMode();
echo $mode;
Ожидаемый результат
webОбычное $mode = 'worker' сразу после global изменило бы глобальное значение. Но здесь локальное имя сначала переведено к temporary. Это ещё одна причина избегать неочевидных перепривязок в конфигурации.
<?php
$handled = 0;
$onHandled = function () use (&$handled): void {
$handled += 1;
};
$onHandled();
$onHandled();
echo $handled;
Ожидаемый результат
2use перечисляет внешние переменные, & связывает handled с ними. Без & функция получила бы значение при создании замыкания и не меняла внешний handled. Здесь два последовательных локальных вызова; это не потокобезопасный счётчик общей очереди.
Не берите ссылку на весь массив $GLOBALS: современные ограничения не разрешают обращаться с ним как с обычной изменяемой копией глобальной таблицы. Если нужен конкретный глобальный элемент, правила и альтернативы разобраны в суперглобальных массивах.
Почему объекты меняются и без &
Этот дополнительный опыт требует понимания класса и экземпляра. Запись ->city читает свойство, new Address создаёт новый объект. Проверим отдельно изменение свойства и замену переменной.
<?php
declare(strict_types=1);
class Address
{
public function __construct(public string $city) {}
}
$first = new Address('Москва');
$second = $first;
$second->city = 'Казань';
echo 'Общий объект: ', $first->city, "\n";
$second = new Address('Тверь');
echo 'После нового объекта: ', $first->city, '/', $second->city, "\n";
$alias =& $first;
$alias = new Address('Омск');
echo 'После замены через ссылку: ', $first->city, "\n";
unset($alias);
Ожидаемый результат
Общий объект: Казань
После нового объекта: Казань/Тверь
После замены через ссылку: Омск
Обычное $second = $first копирует идентификатор того же объекта: оба имени позволяют изменить его city. Но переназначение second на новый Address не меняет first. С =& связь относится к самой переменной: переназначение alias меняет и то, что хранится под именем first. Для независимой копии объекта изучите clone и вложенные объекты.
Поэтому фраза «все объекты автоматически передаются по ссылке» неточна. Один объект доступен через скопированный идентификатор; ссылка =& — другая связь. Подробный опыт с отдельными копиями находится в главе о клонировании.
Типизированное свойство сохраняет свой контракт через ссылку
Ссылка не отменяет ограничения языка. Обычная переменная может менять тип, но ссылка на типизированное свойство связана с его ограничением. Далее нужен класс и объект: public int задаёт целочисленное свойство, стрелка обращается к нему.
<?php
declare(strict_types=1);
class CartLine
{
public int $quantity = 2;
}
$line = new CartLine();
$quantity =& $line->quantity;
try {
$quantity = 'many';
} catch (TypeError $error) {
echo "Тип отклонён\n";
}
echo $line->quantity;
Ожидаемый результат
Тип отклонён
2Некорректная запись не изменила свойство. readonly-свойства и свойства с hooks имеют дополнительные ограничения: не считайте, что любое выражение с -> можно безусловно связать через &.
Как находить неочевидное изменение
| Что заметили | Что проверить |
|---|---|
| Изменилось число после вызова | Знак & у параметра в сигнатуре функции. |
| Последний элемент массива внезапно заменился | Остался ли alias после foreach; есть ли unset сразу после цикла. |
| Изменились оригинал и копия | Вложенные ссылки и общий объект; обычное = не делает deep clone. |
| Переменная исчезла только внутри функции | Локальный unset или перепривязка параметра. |
| Текстовые значения одинаковы | Равенство значений само по себе не доказывает общую ссылку. |
С PHP 7.4 класс ReflectionReference позволяет исследовать ссылку у элемента массива. fromArrayElement возвращает объект описания либо null для обычного элемента; getId даёт непрозрачный идентификатор связи. Это диагностические инструменты после понимания массивов и объектов; не универсальный адрес памяти.
<?php
$quantity = 2;
$data = ['shared' => &$quantity, 'plain' => 2];
$shared = ReflectionReference::fromArrayElement($data, 'shared');
$plain = ReflectionReference::fromArrayElement($data, 'plain');
var_dump($shared !== null);
var_dump($plain === null);
Ожидаемый результат
bool(true)
bool(true)
Оба элемента содержат число 2, но только shared связан с quantity. ===/!== сравнивают также тип. Наличие объекта ReflectionReference само не означает ошибку: ошибка появляется, если общее изменение противоречит вашему контракту.
Проверьте себя. Нужно ли передавать любой большой массив с & для ускорения?
Разбор
Нет. Сначала выберите контракт: изменяет функция исходник или возвращает новый результат. PHP использует copy-on-write; ссылка не является обещанием ускорения и может усложнить изоляцию. Измеряйте реальные данные и окружение, не переносите случайный микротест на весь проект.
Самостоятельная проверка границы изменения
Попробуйте сами. После цикла увеличения [1, 2] на 1 код записал $value = 100. Каким будет массив с unset и без него?
Разбор и проверка
Без снятия ссылки последнее значение станет 100: [2,100]. С unset($value) сразу после foreach массив останется [2,3], а value станет отдельной переменной. Выполните оба варианта в отдельных массивах и сравните вывод json_encode.
Функции и методы этой темы
Откройте карточку, чтобы увидеть договор функции, примеры, ограничения и связанные инструменты.
ReflectionReference::fromArrayElementУзнать, является ли конкретный элемент массива PHP-ссылкой.
PHP 7.4+ · Reflection
public static ReflectionReference::fromArrayElement(array $array, int|string $key): ?ReflectionReferenceЭтот инструмент нужен, когда изменение копии массива неожиданно затронуло оригинал. Сначала прочтите ссылки и вложенные общие элементы. ReflectionReference исследует именно PHP-ссылку, а не факт, что два значения указывают на один объект. Класс доступен с PHP 7.4; его объект получают фабричным методом, а не через обычный new.
| Параметр / результат | Контракт |
|---|---|
| array: array | Массив, существующий элемент которого нужно исследовать. Проверка не создаёт намеренно новую ссылку на этот элемент. |
| key: int|string | Ключ исследуемого элемента, а не его порядковый номер в foreach. |
| ?ReflectionReference | Объект описания для ссылочного элемента, null — для обычного. |
| Неизвестный ключ | ReflectionException. Это другой случай, чем существующий обычный элемент и null в качестве значения. |
<?php
$limit = 5;
$options = ['shared' => &$limit, 'plain' => 5];
var_dump(ReflectionReference::fromArrayElement($options, 'shared') !== null);
var_dump(ReflectionReference::fromArrayElement($options, 'plain') === null);
Ожидаемый результат
bool(true)
bool(true)
:: вызывает статический метод класса. Сначала объект описания существует, затем метод вернул null. Равенство двух чисел 5 не доказывает общую связь. Метод также не исследует все вложенные уровни автоматически.
<?php
$options = ['comment' => null];
var_dump(ReflectionReference::fromArrayElement($options, 'comment'));
try {
ReflectionReference::fromArrayElement($options, 'missing');
} catch (ReflectionException $error) {
echo "Нет ключа\n";
}
Ожидаемый результат
NULL
Нет ключа
comment существует и не является ссылкой: получен null. missing отсутствует: получено исключение. Если отсутствие допустимо по задаче, предварительно используйте array_key_exists, которое различает эти случаи.
Не оборачивайте произвольную переменную в [&$value] для якобы нейтральной проверки её состояния: этим вы сами создаёте дополнительную связь и меняете условия опыта. Для сравнения двух уже найденных ссылок используйте getId. Для исправления обычно нужно убрать ненужный alias или изменить контракт копирования, а не обходить все данные Reflection на каждом запросе.
ReflectionReference::getIdСравнить идентичность двух уже обнаруженных PHP-ссылок.
PHP 7.4+ · Reflection
public ReflectionReference::getId(): stringЭтот инструмент нужен, когда изменение копии массива неожиданно затронуло оригинал. Сначала прочтите ссылки и вложенные общие элементы. ReflectionReference исследует именно PHP-ссылку, а не факт, что два значения указывают на один объект. Класс доступен с PHP 7.4; его объект получают фабричным методом, а не через обычный new.
| Часть контракта | Смысл |
|---|---|
| Аргументы | Метод не принимает аргументов; вызывается у уже полученного ReflectionReference. |
| Возвращает string | Непрозрачный идентификатор ссылочной связи. Это бинарная строка, не адрес и не готовый пользовательский текст. |
| Идентичность | Объекты описания одной связи дают один ID; разные живые связи — разные ID. Сравнение выполняйте строго через ===. |
| Срок использования | Диагностика текущего процесса; не ключ записи в базе и не стабильный ID между запусками. |
<?php
$remaining = 3;
$data = ['left' => &$remaining, 'right' => &$remaining];
$left = ReflectionReference::fromArrayElement($data, 'left');
$right = ReflectionReference::fromArrayElement($data, 'right');
if ($left === null || $right === null) {
throw new LogicException('В примере ожидаются две ссылки');
}
var_dump($left->getId() === $right->getId());
Ожидаемый результат
bool(true)
После проверки null стрелка -> вызывает метод описания. Сравниваем идентификаторы, не сам текст значений. Выводим только bool; печатать бинарный ID в HTML или журнал напрямую не нужно.
<?php
$firstCount = 3;
$secondCount = 3;
$data = ['first' => &$firstCount, 'second' => &$secondCount];
$first = ReflectionReference::fromArrayElement($data, 'first');
$second = ReflectionReference::fromArrayElement($data, 'second');
if ($first === null || $second === null) {
throw new LogicException('В примере ожидаются две ссылки');
}
var_dump($first->getId() === $second->getId());
Ожидаемый результат
bool(false)
Два самостоятельных числа 3 не соединены одним =&. У элементов разные связи, хотя значения совпадают. Для диагностического человекочитаемого отображения строку можно кодировать через bin2hex, но её формат и длину не используйте как бизнес-договор.
Сначала получите описание через fromArrayElement и обработайте null. Для идентичности обычных объектов существуют другие инструменты, например строгое сравнение объектов; ReflectionReference отвечает на другой вопрос. Поиск всех ссылок в сложном графе требует отдельной политики глубины и циклов.
Источники и обсуждения
- PHP: What references do
- PHP: Passing by reference
- PHP: Unsetting references
- PHP: foreach
- PHP: Objects and references
- PHP: language.references
- PHP: language.references.whatare
- PHP: language.references.arent
- PHP: language.references.return
- PHP: language.references.spot
- PHP: class.reflectionreference
- PHP: reflectionreference.fromarrayelement
- PHP: reflectionreference.getid
Код и руководство с собой
Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.
Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.
Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.


