MidТеория3 min

Замыкания и Arrow Functions

Closure, use, bind, fn() =>, first-class callable syntax

Анонимные функции (Closures)

<?php
declare(strict_types=1);

// Anonymous function assigned to variable
$greet = function (string $name): string {
    return "Hello, {$name}!";
};

echo $greet('Alice');  // Hello, Alice!
echo get_class($greet);  // Closure

// As callback
$numbers = [1, 2, 3, 4, 5];
$doubled = array_map(function (int $n): int {
    return $n * 2;
}, $numbers);

// Immediately invoked
$result = (function (): string {
    return 'Immediate!';
})();
echo $result;  // Immediate!

Захват переменных (use)

<?php
declare(strict_types=1);

$multiplier = 3;

// Capture by VALUE (default)
$multiply = function (int $n) use ($multiplier): int {
    return $n * $multiplier;
};

$multiplier = 100;  // Change after closure creation
echo $multiply(5);   // 15 (NOT 500! Captured value at creation time)

// Capture by REFERENCE
$counter = 0;
$increment = function () use (&$counter): void {
    $counter++;
};

$increment();
$increment();
echo $counter;  // 2 — modified via reference!

// Multiple captures
$prefix = 'User';
$suffix = '!';
$format = function (string $name) use ($prefix, $suffix): string {
    return "{$prefix}: {$name}{$suffix}";
};
echo $format('Alice');  // User: Alice!

Ловушка экзамена: use захватывает переменные по значению на момент создания замыкания (не вызова). Изменения переменной после создания НЕ видны внутри замыкания. Для "живой" связи используйте use (&$var).

<?php
declare(strict_types=1);

$value = 'original';

$fn = function () use ($value): string {
    return $value;
};

$value = 'changed';
echo $fn();  // 'original' — captured at creation time!

// With reference
$fn2 = function () use (&$value): string {
    return $value;
};

$value = 'changed again';
echo $fn2();  // 'changed again' — reference captures current value

Arrow Functions (PHP 7.4+)

<?php
declare(strict_types=1);

// Arrow function syntax
$double = fn(int $n): int => $n * 2;
echo $double(5);  // 10

// Arrow functions automatically capture outer variables BY VALUE
$multiplier = 3;
$multiply = fn(int $n): int => $n * $multiplier;
echo $multiply(5);  // 15

$multiplier = 100;
echo $multiply(5);  // 15 — still captured by value at creation!

// Use in callbacks
$numbers = [1, 2, 3, 4, 5];
$even = array_filter($numbers, fn(int $n): bool => $n % 2 === 0);
// [1 => 2, 3 => 4]

// Chaining
$names = ['alice', 'bob', 'charlie'];
$result = array_map(
    fn(string $name): string => ucfirst($name),
    array_filter($names, fn(string $name): bool => strlen($name) > 3)
);

// Arrow functions are single-expression only
// fn($x) => { ... }  // SYNTAX ERROR! No blocks allowed
// Use regular closures for multi-line logic

Отличия Arrow Functions от Closures

Особенность Closure Arrow Function
Синтаксис function() {} fn() =>
Тело Блок {} Одно выражение
Захват Явный use Автоматический
Способ захвата По значению или ссылке Только по значению
Многострочный Да Нет
return Явный Неявный

Ловушка экзамена: Arrow functions захватывают переменные ТОЛЬКО по значению. Нет способа захватить по ссылке в arrow function. Если нужна ссылка — используйте обычную closure с use (&$var).

Closure::bind и Closure::bindTo

<?php
declare(strict_types=1);

class Wallet
{
    private float $balance = 100.0;
}

// Bind closure to an object (access private members)
$getBalance = Closure::bind(
    function (): float {
        return $this->balance;
    },
    new Wallet(),
    Wallet::class  // Scope for accessing private members
);

echo $getBalance();  // 100.0

// bindTo — creates a new closure bound to object
$wallet = new Wallet();
$addMoney = function (float $amount): void {
    $this->balance += $amount;
};

$bound = Closure::bind($addMoney, $wallet, Wallet::class);
$bound(50.0);

echo Closure::bind(
    fn(): float => $this->balance,
    $wallet,
    Wallet::class
)();  // 150.0

// Practical use: helper for testing private methods
function callPrivate(object $obj, string $method, mixed ...$args): mixed
{
    $closure = Closure::bind(
        fn() => $this->$method(...$args),
        $obj,
        $obj::class
    );
    return $closure();
}

First-Class Callable Syntax (PHP 8.1+)

<?php
declare(strict_types=1);

// Create Closure from named function
$fn = strlen(...);
echo $fn('Hello');  // 5

// From static method
class Math
{
    public static function square(int $n): int { return $n ** 2; }
    public function cube(int $n): int { return $n ** 3; }
}

$square = Math::square(...);
echo $square(4);  // 16

// From instance method
$math = new Math();
$cube = $math->cube(...);
echo $cube(3);  // 27

// Practical examples
$numbers = [1, 2, 3, 4, 5];

// Before PHP 8.1:
$result = array_map('intval', $strings);
$result = array_filter($numbers, function (int $n): bool { return $n > 2; });

// PHP 8.1+:
$result = array_map(intval(...), $strings);
$result = array_filter($numbers, fn(int $n): bool => $n > 2);

// With built-in functions
$trimmed = array_map(trim(...), [' hello ', ' world ']);
$upper = array_map(strtoupper(...), ['hello', 'world']);

Генераторы (краткий обзор)

<?php
declare(strict_types=1);

// Generator function uses yield
function fibonacci(): Generator
{
    [$a, $b] = [0, 1];
    while (true) {
        yield $a;
        [$a, $b] = [$b, $a + $b];
    }
}

$fib = fibonacci();
echo $fib->current();  // 0
$fib->next();
echo $fib->current();  // 1

// Use in foreach (lazy iteration)
foreach (fibonacci() as $i => $num) {
    if ($i >= 10) break;
    echo "$num ";
}
// 0 1 1 2 3 5 8 13 21 34

// yield from — delegate to another generator
function inner(): Generator
{
    yield 1;
    yield 2;
    return 'inner result';
}

function outer(): Generator
{
    $result = yield from inner();
    echo $result;  // 'inner result'
    yield 3;
}

Вопросы с экзамена ZCE

Проверь себя

5 из 9

Какие из следующих утверждений верны? (Выберите 3)

Выберите все правильные варианты

Для чего нужен `Closure::bind`?

Каков лучший способ проверить, что `$param` является анонимной функцией, в методе?

Какое из утверждений об операторе `return` верно?

Что выведет код? ```php $x = 1; $fn = function() use ($x) { return $x; }; $x = 2; echo $fn(); ```

Code Challenges

Создание множителя

PHP

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

Test Cases

1. Input: createMultiplier(3)(5)→ Expected: 15
2. Input: createMultiplier(2)(10)→ Expected: 20
3. Input: createMultiplier(0)(100)→ Expected: 0