In PHP, how do fn arrow functions capture outer variables compared with function () use (), and what can an arrow function not do?
answer
- PHP 7.4, fn (...) => expr
- automatic capture, always by value
- cannot modify outer variables
- no use clause, single expression
- nested fn captures through
basics
~20 sAn fn arrow function (PHP 7.4+) is a one-expression closure that captures every outer variable its body uses, automatically and by value. It has no use clause, so it cannot write to outer variables or contain statements.
solid answer
~50 s`fn ($x) => $x * $rate` is a **closure with one expression** that returns that expression's value; it was added in PHP 7.4 and is also a `Closure` object. Instead of a `use` list, the compiler finds each variable the body uses and **captures it by value when the arrow function is created**, as if you had written `use ($rate)` for each. Nested arrow functions capture through each level. Because capture is by value only, an arrow function **cannot modify outer variables**: `fn () => $count++` increments its own copy and the outer `$count` is unchanged. It cannot contain statements such as loops or several lines, and it cannot declare `use`, so by-reference capture needs `function () use (&$x)`. It still supports parameter and return types, defaults, variadics, by-reference parameters, `static fn` and automatic `$this` binding.
code
php · 16 lines<?php
declare(strict_types=1);
$serviceFee = 15;
$withFee = fn (int $price): int => $price + $serviceFee; // captures $serviceFee by value
$serviceFee = 99; // too late: the arrow function kept 15
echo $withFee(200), "\n"; // 215
$checkedIn = 0;
$markArrow = fn () => $checkedIn++; // increments its own copy
$markClosure = function () use (&$checkedIn) { $checkedIn++; };
$markArrow();
$markClosure();
echo $checkedIn, "\n"; // 1: only the by-reference closure changed itgo deeper
Recall the fn (params) => expression form, that it captures outer variables automatically, and that it is limited to one expression.
Explain by-value capture at creation time, why outer variables cannot be modified, and how undefined variables behave compared with an explicit use.
Show when to switch from fn to function () use for accumulators, multi-step logic or explicit dependencies, and how nested capture affects readability.
Set a codebase convention on arrow functions versus full closures that keeps callbacks readable and their dependencies obvious in review.
## Syntax and purpose **Arrow functions**, added in PHP 7.4, are a short form of anonymous function for callbacks that fit in one expression: ```php $fees = array_map(fn (Attendee $a): int => $a->ticketPrice + $serviceFee, $attendees); ``` The form is `fn (parameters) => expression`. The value of the expression is returned; there is no `return` keyword and no braces. Like `function () {}`, an arrow function produces a `Closure` object, so it can be stored, passed and called in the same ways. ## Automatic capture, by value The defining difference is how outer variables get in: | | `function () use (...) {}` | `fn () => ...` | |---|---|---| | How outer variables enter | only those listed in `use` | every variable the body uses, found by the compiler | | Capture mode | by value, or by reference with `&` | **always by value** | | When the value is taken | when the closure is created | when the arrow function is created | | Body | any statements | a single expression | | Can change outer variables | yes, with `use (&$x)` | no | "By value" means the arrow function receives a copy of each outer variable at creation time, exactly like `use ($x)`. The manual describes it as roughly equivalent to writing `use ($x)` for every variable used inside. ## What follows from by-value capture 1. **Outer variables cannot be modified.** `fn () => $count++` increments the arrow function's own copy. After calling it, the outer `$count` is unchanged. 2. **Later outer changes are not seen.** If `$serviceFee` is reassigned after the arrow function was created, the arrow function keeps the old value. 3. **Undefined outer variables are not captured.** Unlike an explicit `use ($x)`, which warns at the definition line, an arrow function silently skips a variable that does not exist yet; reading it inside then raises `Undefined variable` when the arrow function runs. 4. **Objects are still shared.** Capturing an object variable copies its handle, so `fn () => $cart->add($item)` does act on the outer object. ## Nesting Arrow functions capture through nested levels automatically. In `fn ($x) => fn ($y) => $x * $y + $z`, the inner function needs `$z`, so the outer one captures it from the enclosing scope and passes it inward. With `function` closures each level would need its own `use` list. ## What arrow functions still support Everything about the signature works as in other closures: - parameter and return types, default values and variadics: `fn (int ...$ids): array => $ids`; - by-reference parameters and by-reference return: `fn (&$x) => $x`, `fn &($x) => $x`; - automatic `$this` binding inside class methods, and `static fn` to opt out of it; - `throw` in the expression, since `throw` has been an expression since PHP 8.0: `fn () => throw new LogicException('not configured')`. ## What they cannot do - **No `use` clause.** Writing `fn () use ($x) => ...` is a parse error. - **No statements.** A loop, an `if` block with several branches or two assignments in sequence needs `function () {}`; `match` or the ternary operator often fits into one expression instead. - **No by-reference capture**, so accumulators such as a running total must use `function () use (&$total)`. ## Common mistakes - Writing `fn () => $count++` or `fn ($x) => $sum += $x` and expecting the outer variable to change; the result is silently discarded. - Building an arrow function before the variable it needs is assigned, then wondering why it reads `null` with a warning later. - Squeezing multi-step logic into nested ternaries to fit one expression, where a `function` closure with statements would read better. - Assuming an arrow function in a method does not hold `$this`: it binds the object just like a `function` closure, unless declared `static fn`. ## When to use which Reach for `fn` for short, read-only callbacks: mapping, filtering, sorting comparators and small predicates. A typical conference-registration example is a filter that keeps attendees whose ticket type is in an allowed list: `array_filter($attendees, fn (Attendee $a) => in_array($a->ticket, $allowed, true))`. The arrow function captures `$allowed` once, when it is created, and reads it on every call without any risk of changing it. Reach for `function () use (...)` when the body needs statements, when the callback must write back to an outer variable, or when an explicit `use` list documents exactly what the closure depends on.
- In PHP, why does fn () => $total += $x not update the outer $total?An arrow function captures `$total` by value when it is created, so the assignment changes only its own copy, and that copy is discarded when the call ends. The expression's value is returned, but the outer variable is untouched. To accumulate, use `function ($x) use (&$total) { $total += $x; }` or return the new value and assign it at the call site.
- What happens when an arrow function uses a PHP variable that is not defined yet when the arrow function is created?Nothing at creation: implicit capture skips variables that do not exist, without a warning. When the arrow function later runs and reads it, PHP raises `Warning: Undefined variable` and uses `null`, even if the outer variable was defined in the meantime, because it was never captured.
saying these in an interview costs you the question
- Arrow functions capture outer variables by reference, so they can update them.
- An arrow function reads the outer variable's latest value on each call.
- You can add use (&$x) to an fn arrow function when you need a reference.
- Arrow functions cannot declare types or take parameters by reference.
- Arrow functions do not bind $this, so they cannot call methods of the class.