skip to content

Closures & Short Closures

Anonymous functions capture outer variables with use, by value or by reference, while fn arrow functions capture by value automatically. Interviewers probe capture and $this binding.

part ofPHPoverview, primer and where to startread it →
on this pageshow

explore

questions

4

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).
open as a page

In PHP, how do fn arrow functions capture outer variables compared with function () use (), and what can an arrow function not do?

level: middleimportance: must knowfreq 56%

basics

~20 s

An 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.

open as a page

In PHP, what do Closure::bind(), bindTo(), call() and fromCallable() do, and how do they let a closure reach private members?

level: middleimportance: should knowfreq 30%

basics

~10 s

bindTo() and Closure::bind() return a copy of a closure with a new $this and class scope; the scope grants private access. call() binds temporarily and invokes. fromCallable() wraps a callable using the caller's scope.

open as a page

In PHP, when does a closure bind $this automatically, and why would you make event-listener closures static in a long-running worker?

level: seniorimportance: should knowfreq 34%

basics

~20 s

A closure created inside a non-static method binds $this automatically, keeping that object alive as long as the closure lives. A static closure binds no object: it holds only what it captures and refuses later binding to an instance.

open as a page