Перейти к содержимому
Теория · Функции как значения · урок 3 из 8

Захват переменных через use

Как вы уже знаете, функция не видит переменных файла: внутри у неё только параметры и собственные переменные. Анонимная функция устроена так же. Посмотрим, что будет, если она обратится к переменной снаружи:

PHP
<?php

$rate = 2;
$scale = function ($n) {
    return $n * $rate; // ✗
};
echo $scale(3), "\n";
TEXT
Warning: Undefined variable $rate

PHP предупреждает, что внутри функции переменной $rate нет, считает её равной null и печатает 0. Переменная файла для анонимной функции невидима, хотя записана двумя строками выше.

Вернёмся к карточке рецепта. Вы отдаёте её кондитеру, а цены на продукты остались у вас на кухне, и он их не знает.

Нужные цифры вы поэтому пишете прямо на карточке: «молоко — 80 рублей». Кондитер считает по карточке, даже если завтра цена на кухне изменится.

Список use

Анонимная функция получает внешние переменные через список use после параметров:

PHP
<?php

$rate = 2;
$scale = function ($n) use ($rate) {
    return $n * $rate;
};
echo $scale(3), "\n"; // 6
$rate = 10;
echo $scale(3), "\n"; // 6

Давайте разберём по шагам:

  1. use ($rate) перечисляет внешние переменные, которые нужны функции. Несколько переменных пишут через запятую: use ($price, $discount).
  2. В момент создания функции PHP копирует в неё текущее значение $rate — число 2. Это цена, записанная на карточку.
  3. Первый вызов $scale(3) берёт копию и возвращает 6, а echo печатает его.
  4. $rate = 10; меняет внешнюю переменную, а копия внутри функции остаётся 2. Поэтому второй вызов тоже возвращает 6.

Анонимную функцию в PHP называют ещё замыканием — отсюда и имя класса Closure. Слово подходит: функция замыкает в себе захваченные переменные и носит их с собой.

Ссылка вместо копии

Иногда функция должна видеть свежее значение переменной. Для этого перед именем в use ставят &: запись use (&$rate) захватывает не копию, а саму переменную. Знак & значит здесь то же, что у параметра функции: не копию, а переменную вызывающего кода.

На карточке теперь написано не «молоко — 80 рублей», а «цену смотрите на доске». Кондитер каждый раз идёт к доске, и если цену на ней поменяли, он увидит новую.

Сравним оба способа в одной программе. Нажимайте «Следующий шаг» и следите за строкой «захвачена»: у $byValue там копия 2, а у $byRef — сама переменная &$rate.

Вживуюuse по значению и по ссылкешаг 1 из 8 · рамок: 1

В файле создана переменная $rate со значением 2.

Следите за строкой «захвачена» и за $rate в рамке {main}

После $rate = 10 копия в $byValue осталась прежней, и вызов вернул 6. А $byRef при вызове прочитал саму $rate и вернул 30.

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

  • с use ($x) — старое значение: копия снята в момент создания замыкания;
  • с use (&$x) — новое: замыкание связано с самой переменной.

Через use (&$x) замыкание может ещё и изменить внешнюю переменную.

Счётчик на замыкании

Захват по ссылке позволяет функции помнить состояние между вызовами. Вот счётчик:

PHP
$count = 0;
$next = function () use (&$count) {
    $count++;
    return $count;
};
echo $next(), $next(), $next(), "\n"; // 123
echo $count, "\n"; // 3

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

  1. use (&$count) связывает функцию с внешней переменной $count.
  2. Каждый вызов $next() увеличивает саму $count и возвращает новое значение.
  3. Три вызова подряд возвращают 1, 2 и 3, и echo печатает их вместе: 123.
  4. Внешняя $count после них равна 3: функция меняла её, а не копию.

Здесь картинка с доской неточна. По надписи «смотрите на доске» кондитер может только прочитать цену, а замыкание с & пишет на доске само: $count++ меняет переменную снаружи.

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

Счётчик написали с use ($count), без &:

PHP
<?php

$count = 0;
$next = function () use ($count) { // ✗
    $count++;
    return $count;
};
echo $next(), $next(), $next(), "\n";
echo $count, "\n";

Сообщения об ошибке нет, но программа печатает 111 и 0. Замыкание хранит копию 0, и каждый вызов начинает с неё: $count++ увеличивает копию внутри вызова, а после вызова она пропадает.

Внешняя $count не меняется вовсе. Исправление — use (&$count), как в счётчике выше.

Главное

  • Анонимная функция не видит внешних переменных; нужные перечисляют в use.
  • use ($x) копирует значение в момент создания замыкания, и поздние изменения $x замыкание не видит.
  • use (&$x) связывает замыкание с самой переменной: оно видит изменения и может менять её.
  • Счётчик между вызовами держится на use (&$count); с use ($count) каждый вызов начинает с той же копии.