Захват переменных через use
Как вы уже знаете, функция не видит переменных файла: внутри у неё только параметры и собственные переменные. Анонимная функция устроена так же. Посмотрим, что будет, если она обратится к переменной снаружи:
<?php
$rate = 2;
$scale = function ($n) {
return $n * $rate; // ✗
};
echo $scale(3), "\n";Warning: Undefined variable $ratePHP предупреждает, что внутри функции переменной $rate нет, считает её равной null и печатает 0. Переменная файла для анонимной функции невидима, хотя записана двумя строками выше.
Вернёмся к карточке рецепта. Вы отдаёте её кондитеру, а цены на продукты остались у вас на кухне, и он их не знает.
Нужные цифры вы поэтому пишете прямо на карточке: «молоко — 80 рублей». Кондитер считает по карточке, даже если завтра цена на кухне изменится.
Список use
Анонимная функция получает внешние переменные через список use после параметров:
<?php
$rate = 2;
$scale = function ($n) use ($rate) {
return $n * $rate;
};
echo $scale(3), "\n"; // 6
$rate = 10;
echo $scale(3), "\n"; // 6Давайте разберём по шагам:
use ($rate)перечисляет внешние переменные, которые нужны функции. Несколько переменных пишут через запятую:use ($price, $discount).- В момент создания функции PHP копирует в неё текущее значение
$rate— число2. Это цена, записанная на карточку. - Первый вызов
$scale(3)берёт копию и возвращает6, аechoпечатает его. $rate = 10;меняет внешнюю переменную, а копия внутри функции остаётся2. Поэтому второй вызов тоже возвращает6.
Анонимную функцию в PHP называют ещё замыканием — отсюда и имя класса Closure. Слово подходит: функция замыкает в себе захваченные переменные и носит их с собой.
Ссылка вместо копии
Иногда функция должна видеть свежее значение переменной. Для этого перед именем в use ставят &: запись use (&$rate) захватывает не копию, а саму переменную. Знак & значит здесь то же, что у параметра функции: не копию, а переменную вызывающего кода.
На карточке теперь написано не «молоко — 80 рублей», а «цену смотрите на доске». Кондитер каждый раз идёт к доске, и если цену на ней поменяли, он увидит новую.
Сравним оба способа в одной программе. Нажимайте «Следующий шаг» и следите за строкой «захвачена»: у $byValue там копия 2, а у $byRef — сама переменная &$rate.
1<?php2 3$rate = 2;4$byValue = function ($n) use ($rate) {5 return $n * $rate;6};7$byRef = function ($n) use (&$rate) {8 return $n * $rate;9};10$rate = 10;11echo $byValue(3), "\n";12echo $byRef(3), "\n";
В файле создана переменная $rate со значением 2.
После $rate = 10 копия в $byValue осталась прежней, и вызов вернул 6. А $byRef при вызове прочитал саму $rate и вернул 30.
Этот вопрос часто задают на собеседовании: «Что увидит замыкание, если переменную изменили после его создания?» Ответ:
- с
use ($x)— старое значение: копия снята в момент создания замыкания; - с
use (&$x)— новое: замыкание связано с самой переменной.
Через use (&$x) замыкание может ещё и изменить внешнюю переменную.
Счётчик на замыкании
Захват по ссылке позволяет функции помнить состояние между вызовами. Вот счётчик:
$count = 0;
$next = function () use (&$count) {
$count++;
return $count;
};
echo $next(), $next(), $next(), "\n"; // 123
echo $count, "\n"; // 3Разберём по шагам:
use (&$count)связывает функцию с внешней переменной$count.- Каждый вызов
$next()увеличивает саму$countи возвращает новое значение. - Три вызова подряд возвращают
1,2и3, иechoпечатает их вместе:123. - Внешняя
$countпосле них равна3: функция меняла её, а не копию.
Здесь картинка с доской неточна. По надписи «смотрите на доске» кондитер может только прочитать цену, а замыкание с & пишет на доске само: $count++ меняет переменную снаружи.
Частая ошибка
Счётчик написали с use ($count), без &:
<?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)каждый вызов начинает с той же копии.