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

Анонимные функции и callable

Чему вы научитесь

Чему вы научитесь:

  • хранить функцию в переменной и передавать её в другую функцию через параметр callable;
  • захватывать внешние переменные через use и отличать копию значения от use (&$x);
  • писать короткие стрелочные функции fn и делать замыкание из готовой функции записью strlen(...);
  • сортировать массив по своему правилу с помощью usort и оператора <=>.
Карточка рецепта fn с прикреплённой баночкой use скользит по столу к поварскому колпаку usort

В первой теме функция была карточкой с рецептом: основной рецепт отсылал к карточке крема, повар готовил по ней и возвращался с результатом. Такая карточка лежит на своём месте, и её зовут по имени.

Теперь представьте, что карточку можно взять в руки и отдать другому человеку. Кондитер не знает заранее, какую начинку вы хотите. Вы протягиваете ему карточку с рецептом, и он готовит по ней.

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

PHP
<?php

$double = function (int $n): int {
    return $n * 2;
};

echo $double(4), "\n"; // 8
echo $double(10), "\n"; // 20
echo gettype($double), "\n"; // object

Давайте разберём программу по строкам:

  1. function (int $n): int { … } — функция без имени. У неё есть параметр, тип результата и тело, как у обычной функции.
  2. $double = …; кладёт эту функцию в переменную $double. Точка с запятой после } закрывает присваивание.
  3. $double(4) вызывает функцию из переменной: круглые скобки с аргументом стоят сразу после имени переменной. Вызов возвращает 8.
  4. echo gettype($double) печатает object: функция в переменной — значение особого вида, объект встроенного класса Closure. Классы и объекты разберём в теме 11, а пока достаточно знать, что такое значение можно вызвать.

Функция без имени

Анонимная функция — это функция без имени, которую записывают там, где нужно значение. Её можно присвоить переменной, передать аргументом или вернуть из другой функции через return.

От функции с именем она отличается записью:

  • после слова function сразу идут круглые скобки с параметрами, а имени нет;
  • вся запись — выражение, поэтому в присваивании после } нужна ;.

Есть и разница во времени. Функцию с именем PHP запоминает до запуска, и вызов может стоять выше объявления. Анонимная функция появляется, только когда выполнится строка с присваиванием, поэтому вызов $double выше этой строки закончится ошибкой.

Здесь картинка с карточкой неточна. Чтобы отдать настоящую карточку двоим, её пришлось бы переписать. А после $g = $double копии не появляется: обе переменные держат один и тот же объект Closure.

Параметр callable

Теперь передадим функцию в другую функцию. Функция apply получает функцию $f и число $x и возвращает результат вызова $f($x):

PHP
function apply(callable $f, int $x): int
{
    return $f($x);
}

echo apply($double, 7), "\n"; // 14
echo apply(function (int $n): int {
    return $n + 1;
}, 7), "\n"; // 8

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

  1. callable $f — тип параметра: сюда можно передать только то, что можно вызвать.
  2. Внутри apply вызов $f($x) запускает переданную функцию с аргументом $x.
  3. apply($double, 7) передаёт функцию из переменной без скобок. Она не вызывается сразу, а уходит в apply, и та возвращает 14.
  4. Во втором вызове анонимная функция записана прямо в скобках вызова. apply вызывает её с 7 и возвращает 8.

Функция apply сама решает, когда вызвать $f. Так и кондитер сам решает, когда готовить по вашей карточке. Значение, которое можно вызвать круглыми скобками, называют вызываемым значением.

Тип callable пропускает в функцию только вызываемые значения. Если передать в apply число, PHP остановит программу с TypeError ещё до первой строки тела.

Имя функции в строке

Вызываемым значением бывает и строка с именем функции.

Возьмём встроенную функцию abs: она возвращает модуль числа — его расстояние от нуля. abs(-5) равно 5, и abs(5) тоже 5. Передадим её в apply по имени:

PHP
echo apply('abs', -5), "\n"; // 5
$name = 'strtoupper';
echo $name('tea'), "\n"; // TEA
var_dump(is_callable('abs')); // bool(true)
var_dump(is_callable('shout')); // bool(false)

Что здесь происходит:

  1. apply('abs', -5) передаёт строку 'abs'. Тип callable её пропускает, потому что это имя существующей функции, и вызов $f($x) внутри apply превращается в abs(-5).
  2. $name('tea') вызывает функцию, имя которой лежит в переменной, то есть strtoupper('tea').
  3. is_callable проверяет, можно ли вызвать значение, и возвращает true или false. Функции shout в программе нет, поэтому вторая проверка печатает bool(false).

Строку с именем удобно передавать, когда нужная функция уже есть: не придётся писать анонимную, которая только вызывает её.

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

После } анонимной функции забыли точку с запятой:

PHP
<?php

$double = function (int $n): int {
    return $n * 2;
} // ✗
echo $double(4), "\n";
TEXT
Parse error: syntax error, unexpected token "echo"

PHP читает присваивание дальше и вместо ; встречает echo. Ошибка разбора не даёт запуститься всему файлу: не выполнится ни одна строка.

У функции с именем после } точку с запятой не ставят, поэтому здесь её часто забывают. Исправление — };, как в первом примере.

Главное

  • Анонимная функция — функция без имени; её кладут в переменную и вызывают скобками: $double(4).
  • В присваивании после } анонимной функции нужна ;.
  • Параметр callable принимает вызываемое значение, и функция запускает его как $f($x).
  • Строка с именем функции, например 'abs', тоже вызываемое значение: проверить его можно через is_callable.