Замикання в PHP — це не окрема мовна конструкція «функція всередині функції», а звичайний обʼєкт класу Closure. Тому його можна покласти в змінну, передати аргументом, повернути з методу, зберегти у властивості й викликати як $fn(...). Ключова властивість — воно памʼятає частину контексту, у якому було оголошене. Але «памʼятає» не автоматично: анонімна функція, оголошена через function, бачить лише свої параметри, глобальні суперглобали й те, що ви явно перелічили в use. Це принципова відмінність від JavaScript, де замикання затягує всю зовнішню область видимості; у PHP список захоплення пишеться руками.
Найважливіший момент, на якому валяться на співбесідах: use ($x) спрацьовує в момент оголошення замикання, а не в момент виклику. PHP копіює поточне значення $x у внутрішнє сховище обʼєкта Closure, і подальші зміни зовнішньої змінної на нього не впливають — саме це показує перший блок коду. Якщо потрібен звʼязок в обидва боки, пишуть use (&$x): тоді замикання і зовнішній код працюють з однією змінною, і присвоєння всередині видно назовні. Окремо варто розрізняти семантику типів: копіювання змінної, що тримає обʼєкт, копіює лише ідентифікатор, тому зміни властивостей обʼєкта видно й без &; масив же копіюється по-справжньому, і зовнішнє додавання елемента в замикання не потрапить.
Стрілочні функції зʼявилися в PHP 7.4 і закривають найчастіший випадок — коротке перетворення в array_map, usort, колекціях Laravel. Синтаксис fn (аргументи) => вираження має три жорсткі властивості. По-перше, захоплення неявне: усі зовнішні змінні, згадані в тілі, захоплюються автоматично, use для fn не існує і є синтаксичною помилкою. По-друге, захоплення завжди за значенням — накопичувач на fn не побудувати, для цього лишається function () use (&$total). По-третє, тіло — рівно одне вираження, яке автоматично і повертається: ні {}, ні return, ні кількох інструкцій там бути не може. Типи параметрів і повернення при цьому дозволені (fn (int $n): int => ...), і зловживати їх пропуском не варто.
Окрема тема — $this. Замикання, оголошене в методі, автоматично привʼязує поточний обʼєкт і область видимості класу, тому всередині доступні навіть private-властивості — це видно на $withTitle у прикладі. Слово static перед function або fn цю привʼязку знімає: $this стає недоступним, а спроба привʼязати обʼєкт через bindTo() дасть попередження і null. Практичний мотив писати static — час життя: замикання зі звʼязаним $this утримує обʼєкт у памʼяті, і для довгоживучих callback-ів (реєстрація в контейнері, слухачі подій, кеш замикань) це зайве утримання. У зворотний бік працюють Closure::bind() / bindTo() / Closure::call(), які підставляють інший $this і scope; на цьому побудовані макроси Laravel.
Межі варто назвати чесно. Замикання не бачить змінних зовнішньої функції без use, і global тут не заміна — це інший механізм із іншими проблемами. fn не є «кращим function»: щойно потрібні дві інструкції, try/catch або посилання, повертаються до повної форми. І в обох випадках залишається питання читабельності списку захоплення: якщо в use більше двох-трьох змінних, це майже завжди сигнал, що замість замикання просився окремий метод або невеликий клас із __invoke().
$multiplier = 3;
// use копіює ЗНАЧЕННЯ в момент оголошення замикання
$byValue = function (int $n) use ($multiplier): int {
return $n * $multiplier;
};
// fn (PHP 7.4+) захоплює автоматично і теж лише за значенням
$arrow = fn (int $n): int => $n * $multiplier;
$multiplier = 10; // змінюємо ПІСЛЯ оголошення
echo $byValue(2); // 6 — захоплено ще трійку
echo $arrow(2); // 6 — так само
// use (&$x): замикання і зовнішній код ділять одну змінну
$total = 0;
$accumulate = function (int $price) use (&$total): void {
$total += $price; // пишемо назовні
};
$accumulate(100);
$accumulate(50);
echo $total; // 150
final class Report
{
private string $title = 'Звіт';
/** @return array<int, string> */
public function render(array $rows): array
{
// звичайне замикання само привʼязує $this — приватне поле доступне
$withTitle = fn (string $row): string => "{$this->title}: {$row}";
// static прибирає привʼязку: $this всередині недоступний
$trim = static fn (string $row): string => trim($row);
return array_map($withTitle, array_map($trim, $rows));
}
}
$counter = Closure::bind(
function (): string { return $this->title; }, // $this підставимо ззовні
new Report(),
Report::class // scope дає доступ до private
);
echo $counter(); // 'Звіт'
Одна фраза, яка закриває питання: «`function () use ($x)` — явне захоплення, за значенням, або за посиланням через `&`; `fn () => $x` — неявне захоплення, тільки за значенням, тільки одне вираження». Далі додайте, що `$this` захоплюється в обох, і зняти його можна словом `static`.