Перейти к содержимому
Теория · Функции · урок 6 из 11

По значению и по ссылке

Представьте, что автомат не забирает ваши монеты, а снимает с них точные копии и работает с копиями. Что бы он ни делал внутри, монеты в вашем кошельке остаются прежними.

Функция в PHP устроена так же: она получает копию каждого аргумента. Давайте проверим это на массиве — корзине покупок:

PHP
<?php

function addItem(array $list, string $item): array
{
    $list[] = $item;
    return $list;
}

$cart = ['tea'];
$bigger = addItem($cart, 'milk');
echo count($cart), ' ', count($bigger), "\n"; // 1 2

Разберём по шагам:

  1. addItem($cart, 'milk') передаёт в функцию копию массива $cart.
  2. $list[] = $item; добавляет 'milk' в копию, и в $list теперь два элемента.
  3. return $list; возвращает изменённую копию, и она попадает в $bigger.
  4. echo печатает 1 2: в $cart по-прежнему один элемент.

Параметр получает копию

Такой способ называют передачей по значению: функция получает копию аргумента. Как вы уже знаете, $b = $a копирует массив целиком. Параметр получает аргумент так же, как переменная при присваивании, поэтому массив внутри функции — отдельный массив. С числами и строками то же самое.

Чтобы изменение дошло до вызывающего кода, функция возвращает новое значение, а вызывающий код сохраняет его: $cart = addItem($cart, 'milk');.

В жизни снять копию с монеты нельзя, а PHP делает это незаметно и экономно. Настоящая копия массива появляется, только когда функция в него пишет, поэтому передать большой массив в функцию дёшево.

Передача по ссылке: &

Иногда функция должна изменить саму переменную вызывающего кода. Для этого перед именем параметра ставят &. Это передача по ссылке: параметр становится вторым именем той переменной, которую передали в вызов. В картинке с автоматом & — это отдать автомату сам кошелёк.

PHP
function addBonus(int &$score): void
{
    $score = $score + 10;
}

$points = 50;
addBonus($points);
addBonus($points);
echo $points, "\n"; // 70

Давайте разберём:

  1. int &$score — параметр по ссылке: $score внутри функции и $points снаружи — два имени одной переменной.
  2. Каждый вызов прибавляет 10 прямо к $points.
  3. После двух вызовов echo печатает 70.

Знак & пишут только в объявлении функции, а в вызове стоит обычное имя переменной: addBonus($points). Передачу по ссылке используют редко: функцию, которая возвращает новое значение, проще читать.

Что ответить на собеседовании

Этот вопрос часто задают на собеседовании: «PHP передаёт аргументы по значению или по ссылке?» Ответ:

  • по умолчанию по значению: функция получает копию, и массивы тоже;
  • по ссылке — только если у параметра стоит &;
  • объекты (о них — тема 11) тоже передаются по значению: копируется идентификатор — номер, по которому PHP находит объект, — поэтому функция работает с тем же объектом.

Знак & в foreach

Знак & встречается и в цикле foreach. Запись foreach ($prices as &$price) отдаёт в цикл сам элемент, а не копию, и присваивание меняет массив:

PHP
$prices = [10, 20, 30];
foreach ($prices as &$price) {
    $price = $price * 2;
}
unset($price);
echo implode(' ', $prices), "\n"; // 20 40 60

После цикла $price всё ещё связана с последним элементом массива. Если позже записать что-то в $price, например в следующем foreach с тем же именем, испортится последний элемент. Поэтому сразу после такого цикла пишут unset($price): он разрывает связь и не трогает массив.

Частая ошибка

В параметр по ссылке передали готовое число, а не переменную:

PHP
addBonus(50); // ✗
TEXT
Fatal error: Uncaught Error: addBonus(): Argument #1 ($score) could not be passed by reference

Параметру по ссылке нужна переменная, которую функция сможет изменить, а 50 — готовое значение, и изменение негде сохранить. Программа останавливается на этой строке. Исправление — передать переменную: $points = 50;, затем addBonus($points);.

Главное

  • По умолчанию функция получает копию аргумента, и массивы тоже: изменения внутри не видны снаружи.
  • Параметр &$score — второе имя переменной из вызова, и запись в него меняет эту переменную.
  • В параметр по ссылке передают переменную, а не готовое значение.
  • После foreach со знаком & переменную цикла убирают через unset.