Замыкания в PHP: анонимные и стрелочные функции (use, примеры и лучшие практики)

Замыкания в PHP: анонимные и стрелочные функции (use, примеры и лучшие практики)

Замыкания в PHP: анонимные и стрелочные функции (use, примеры и лучшие практики)

Поисковый запрос, под который оптимизирована статья: «Замыкания в PHP» и «Анонимные и стрелочные функции в PHP». Если вы хотите писать компактный и выразительный код, замыкания (closures) — то, что нужно. Разберёмся на понятных примерах, когда выбирать анонимные функции, а когда — стрелочные, как работает use, в чём разница между захватом по значению и по ссылке, и какие ошибки встречаются чаще всего.

Что такое замыкание в PHP

Замыкание — это функция, которая «замыкает» (захватывает) переменные из внешней области видимости. В PHP это анонимные функции (Closure) и стрелочные функции (fn), появившиеся в PHP 7.4.

$greet = function (string $name): string {
    return "Привет, $name!";
};

echo $greet('Алиса'); // Привет, Алиса!

Оператор use: захват переменных

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

$prefix = 'ID-';

$makeId = function (int $n) use ($prefix): string {
    return $prefix . str_pad((string)$n, 5, '0', STR_PAD_LEFT);
};

echo $makeId(42); // ID-00042

Важно: по умолчанию use копирует переменную по значению. Изменение внутри функции не повлияет на внешнюю.

$x = 10;
$fn = function () use ($x) {
    $x++;
    echo $x; // 11
};

$fn();
echo $x; // 10 (внешняя переменная не изменилась)

Захват по ссылке

Если нужно изменить внешнюю переменную, захватывайте по ссылке через &.

$count = 0;
$inc = function () use (&$count) {
    $count++;
};

$inc();
$inc();
echo $count; // 2

Стрелочные функции (fn) и чем они отличаются

Стрелочные функции — это короткий синтаксис для простых выражений. Они автоматически захватывают переменные по значению (как будто с use), но не поддерживают многострочные тела и нельзя сделать захват по ссылке.

$factor = 10;
$mul = fn(int $n): int => $n * $factor; // $factor уже доступен, use не нужен

echo $mul(5); // 50

Если нужно мутировать внешнюю переменную — используйте обычную анонимную функцию с use (&$var).

Практика: колбэки для массивов

Замыкания отлично подходят для array_map, array_filter, usort и т. п.

$nums = [1, 2, 3, 4, 5];
$k = 3;
$scaled = array_map(fn($n) => $n * $k, $nums); // [3, 6, 9, 12, 15]

$words = ['php', 'Laravel', 'symfony', 'CodeIgniter'];
$onlyLower = array_filter($words, fn($w) => $w === strtolower($w)); // ['php']

$users = [
  ['name' => 'Ann', 'age' => 19],
  ['name' => 'Bob', 'age' => 25],
  ['name' => 'Eve', 'age' => 21],
];
usort($users, fn($a, $b) => $a['age']  $b['age']);
// Теперь по возрастанию возраста

Фабрики и замыкания: параметризация поведения

Замыкание удобно возвращать из функции, чтобы сконструировать «на лету» поведение с параметрами.

function makeValidator(int $min, int $max): Closure {
    return function (int $n) use ($min, $max): bool {
        return $n >= $min && $n <= $max;
    };
}

$ageValidator = makeValidator(18, 30);
var_dump($ageValidator(21)); // true
var_dump($ageValidator(31)); // false

Кеширование результатов (мемоизация) с замыканиями

Иногда функция часто вызывается с одними и теми же аргументами. Замыкание позволяет легко добавить кеш.

function memoize(callable $fn): Closure {
    $cache = [];
    return function (...$args) use ($fn, &$cache) {
        $key = md5(serialize($args));
        if (!array_key_exists($key, $cache)) {
            $cache[$key] = $fn(...$args);
        }
        return $cache[$key];
    };
}

$slowFib = function (int $n) use (&$slowFib): int {
    return $n < 2 ? $n : $slowFib($n - 1) + $slowFib($n - 2);
};

$fib = memoize($slowFib);
echo $fib(35); // значительно быстрее при повторных вызовах

Замыкания и $this: привязка контекста

Внутри метода анонимная функция видит $this. Но можно жёстко отвязать контекст, объявив функцию как static — тогда $this внутри недоступен.

class Repo {
    private array $items = [1, 2, 3];

    public function each(callable $fn): void {
        foreach ($this->items as $i) {
            $fn($i); // внутри колбэка доступен $this, если он не static
        }
    }
}

$r = new Repo();
$r->each(function ($i) {
    echo $this instanceof Repo ? "okn" : "non"; // ok
});

$r->each(static function ($i) {
    echo isset($this) ? 'есть this' : 'нет this'; // нет this
});

Типы: callable vs Closure

callable — любой вызваемый тип (имя функции, [объект, метод], замыкание и т. п.). Closure — именно объект анонимной функции. Если функция должна принимать только замыкание — указывайте Closure.

function runTwice(Closure $job): void {
    $job();
    $job();
}

runTwice(function () { echo 'Hi '; }); // Hi Hi 

Подводные камни и частые ошибки

  • Забытый use. Анонимная функция не видит внешние переменные без use (кроме стрелочных функций).
  • Нужна мутация — захватывайте по ссылке: use (&$var).
  • Стрелочные функции — только одно выражение. Нужна логика в несколько строк — используйте обычную.
  • Сериализация: Closure по умолчанию не сериализуется. Не кладите замыкания в сессию/кеш как данные.
  • Производительность: не создавайте замыкания в тесных циклах без необходимости — вынесите наружу или переиспользуйте.
  • Реальный пример: динамическая валидация формы

    Допустим, требуется гибкая валидация нескольких полей. Замыкания позволяют описать правила декларативно.

    $rules = [
      'email' => function (string $v): bool { return $v !== '' && str_contains($v, '@'); },
      'age'   => function (string $v): bool { $n = (int)$v; return $n >= 18 && $n <= 65; },
    ];
    
    function validate(array $input, array $rules): array {
        $errors = [];
        foreach ($rules as $field => $rule) {
            $value = $input[$field] ?? '';
            if (!$rule($value)) {
                $errors[$field] = 'Некорректное значение';
            }
        }
        return $errors;
    }
    
    $input = ['email' => 'user@example.com', 'age' => '20'];
    $errors = validate($input, $rules);
    var_dump($errors); // []
    

    Лучшие практики

  • Старайтесь делать колбэки чистыми (без побочных эффектов), когда это возможно.
  • Для коротких преобразований используйте стрелочные функции — это компактно и читаемо.
  • Для сложной логики и мутаций выбирайте обычные анонимные функции с use.
  • Явно типизируйте аргументы и возвращаемые значения — ошибки ловятся раньше.
  • Повторно используйте замыкания: сохраняйте в переменные, передавайте в функции высшего порядка.
  • Итоги

    Замыкания — фундаментальный инструмент PHP, позволяющий писать выразительный и модульный код. Освойте use, разницу между анонимными и стрелочными функциями, и вы заметите, как колбэки в массивах, валидация, фабрики и кеширование станут проще.

    Хотите системно прокачать PHP и базу данных с практикой? Рекомендую посмотреть программу и начать обучение на курсе «PHP и MySQL с Нуля до Гуру 3.0 — запишитесь и начните сейчас».

    Источник

    НЕТ КОММЕНТАРИЕВ

    Оставить комментарий