skip to content

In PHP, how does an anonymous function reach variables of the enclosing scope through use, and what changes with use (&$x)?

level: juniorimportance: must knowfreq 66%

answer

  1. no automatic access with function ()
  2. value copied when the closure is created
  3. & shares the variable itself
  4. no $this, superglobals or parameter names
  5. 8.5: Closure::getCurrent() for recursion

basics

~20 s

A function () closure sees no outer variables unless listed in use. use ($x) copies the value when the closure is created; use (&$x) binds the variable itself, so later changes flow both ways between closure and outer scope.

solid answer

~40 s

A PHP anonymous function has its own scope: an outer variable is visible inside it only if it is named in the `use` clause, and forgetting it gives an `Undefined variable` warning at call time. `use ($count)` copies the variable's value **when the closure object is created**, so later outer assignments are not seen and assignments inside change only the closure's copy. `use (&$count)` binds the closure to the **same variable**, so updates flow both ways, which is how a listener can increment a counter owned by the code that registered it. The list may not contain `$this`, a superglobal, or a name used by one of the closure's own parameters; each is a compile error. A recursive closure traditionally captured itself with `use (&$fn)`; since PHP 8.5, `Closure::getCurrent()` returns the running closure instead.

code

php · 23 lines
php
<?php
declare(strict_types=1);

$registered = 0;
$eventName = 'conference.registered';
$listeners = [];

// by reference: the listener updates the caller's counter
$listeners[] = function (string $email) use (&$registered): void {
    $registered++;
};
// by value: $eventName is frozen at creation time
$listeners[] = function (string $email) use ($eventName): void {
    echo "$eventName: $email\n";
};

$eventName = 'renamed.later';
foreach ($listeners as $listener) {
    $listener('[email protected]');
}
echo $registered, "\n";
// conference.registered: [email protected]
// 1

go deeper

for a junior

Recall that outer variables must be listed in use, that use ($x) copies the value at creation, and that use (&$x) shares the variable.

for a middle

Explain when the copy happens, what happens with undefined variables in each form, and which names use rejects at compile time.

for a senior

Show when a by-reference capture is a deliberate accumulator and when it hides shared mutable state that makes callbacks hard to reason about.

for a principal

Set conventions for callbacks in a codebase: by-value capture as the default, explicit accumulators, and Closure::getCurrent() for recursion on 8.5.

## A closure has its own scope PHP's anonymous functions, written `function (...) { ... }`, are objects of the built-in `Closure` class. Like every PHP function they start with an **empty local scope**: unlike some languages, PHP does not let the body see the variables around it automatically. To bring one in you list it in the **`use` clause**, which sits between the parameter list and the return type: ```php $listeners[] = function (string $email) use ($eventName): void { /* ... */ }; ``` If the body reads a variable that is neither a parameter nor in `use`, PHP raises `Warning: Undefined variable $eventName` when the closure runs, and the value is `null`. ## By value: `use ($x)` `use ($x)` **copies the current value** of `$x` into the closure at the moment the `function` expression is evaluated, which is when the `Closure` object is created. Consequences: - reassigning `$x` outside after that point does not affect the closure; - assigning to `$x` inside the closure changes only that call's local copy; the next call of the same closure starts again from the captured value; - if `$x` is undefined when the closure is created, PHP warns right there, at the definition line, and captures `null`. Arrays follow copy-on-write, so capturing a large array by value is cheap as long as neither side modifies it. ## By reference: `use (&$x)` `use (&$x)` binds the closure's `$x` and the outer `$x` to the **same variable**: - changes made outside after creation are visible inside; - changes made inside are visible outside; - if `$x` did not exist yet, it is created as `null` without a warning. That is the tool when a callback must report back to the code that registered it, for example counting registrations or collecting e-mails. | Clause | Value seen inside | Assignment inside affects outside? | Undefined outer variable | |---|---|---|---| | none | `null` plus a warning at call time | no | n/a | | `use ($x)` | value at creation time | no | warning at creation, captures `null` | | `use (&$x)` | current value at call time | yes | created as `null`, no warning | ## What `use` may not contain Since PHP 7.1 three kinds of names are rejected when the file compiles: 1. `$this`: `Cannot use $this as lexical variable`. Inside a class the closure gets `$this` automatically instead. 2. superglobals such as `$_POST`: `Cannot use auto-global as lexical variable`. They are visible everywhere anyway. 3. a name that is also one of the closure's parameters: `Cannot use lexical variable $email as a parameter name`. ## Recursive closures A closure has no name to call itself by. The classic idiom captures the variable that will hold it **by reference**, because by value the variable would still be undefined at creation time: ```php $countdown = function (int $n) use (&$countdown): void { if ($n > 0) { echo $n; $countdown($n - 1); } }; ``` PHP 8.5 added `Closure::getCurrent()`, which returns the currently executing closure and throws `Error: Current function is not a closure` elsewhere. It removes the need for the self-reference and the shared variable that could be reassigned by accident. ## Objects captured by value Capturing an object variable by value copies the **handle**, not the object, so method calls and property writes through it reach the same object the outer code sees. Only reassigning the variable to a different object stays local. `use (&$obj)` is needed only when the closure must replace the outer variable itself. ## Choosing between them - Default to **by value**: the closure's behaviour is fixed when it is created, and readers do not have to track later writes. - Use **by reference** for deliberate accumulators and for self-reference in pre-8.5 code. - For one-expression callbacks that only read outer values, an `fn` arrow function captures by value automatically and needs no `use` at all. ## Mistakes that show up in review - **Missing `use`**: the body reads `$eventName`, the clause does not list it, and the closure runs with `null` and only a warning in the log to show for it. - **Expecting live values from `use ($x)`**: a listener registered early in a bootstrap script reports a configuration value that was changed later. - **Accidental sharing with `use (&$x)`**: several closures built in a loop all capture the same loop variable by reference and all see its last value. - **Capturing more than needed**: listing a large context array when the closure reads one key keeps the whole array alive as long as the closure lives.

  • In PHP, when is the value captured by use ($x) taken if the closure is created inside a loop?
    On each loop iteration that evaluates the `function` expression, a new `Closure` object is created and copies the value `$x` has at that moment. So closures built in a loop with `use ($i)` each keep their own `$i`. With `use (&$i)` they all share the one loop variable and see its final value after the loop.
  • What does Closure::getCurrent() replace in PHP 8.5, and what happens if you call it outside a closure?
    It returns the closure that is currently executing, so a recursive closure can call itself without capturing its own variable via `use (&$fn)`. Called from ordinary code or a named function, it throws `Error: Current function is not a closure`.

saying these in an interview costs you the question

  • A PHP closure automatically sees every variable of the enclosing function.
  • use ($x) reads the outer variable's current value on every call.
  • Writing to a by-value captured variable changes the outer variable.
  • You write use ($this) to access the object inside a method's closure.
  • A recursive closure can capture itself by value with use ($fn).