Closures & use()
A closure is an anonymous function that has "closed over" some variables from the scope where it was created, keeping access to them even after that outer scope has finished running. PHP does not do this automatically for regular anonymous functions — you have to opt in with a use() clause, listing exactly which outer variables the function should capture. That explicitness is a deliberate design choice: it makes it obvious, just by reading the function signature, which pieces of outer state a callback depends on.
Capturing by value with use ($var)
By default, use ($var) captures a copy of the variable's value at the moment the closure is created. Anything that happens to the original variable afterwards has no effect on the copy sitting inside the closure.
By-value capture: the closure freezes the value
<?php
$x = 10;
$printX = function () use ($x) {
echo "Inside closure: {$x}\n";
};
$x = 99; // changed AFTER the closure was created
$printX();
echo "Outside closure: {$x}\n";Inside closure: 10 Outside closure: 99
The closure printed 10, the value $x held when use ($x) ran — not 99, the value it holds now. The capture happened once, at creation time, and was never updated afterward.
Capturing by reference with use (&$var)
Prefixing the variable with & inside use() captures it by reference instead. The closure and the outer scope then share the exact same storage location, so a change on either side is visible on both.
By-reference capture: the closure sees later changes
<?php
$x = 10;
$printX = function () use (&$x) {
echo "Inside closure: {$x}\n";
};
$x = 99; // changed AFTER the closure was created
$printX();
echo "Outside closure: {$x}\n";Inside closure: 99 Outside closure: 99
Same code shape, one character different (&$x instead of $x), and the result flips completely. This is the single most important distinction to internalize about closures in PHP.
A closure factory: the counter generator
A common and genuinely useful pattern is a function that returns a closure, where that closure keeps its own private, persistent state via a by-reference capture. Each call to the outer function produces an independent counter.
A function that manufactures independent counters
<?php
function makeCounter() {
$count = 0;
return function () use (&$count) {
$count++;
return $count;
};
}
$counterA = makeCounter();
$counterB = makeCounter();
echo $counterA(), "\n";
echo $counterA(), "\n";
echo $counterA(), "\n";
echo $counterB(), "\n"; // independent from $counterA1 2 3 1
Each call to makeCounter() creates a brand new $count variable that only the returned closure can see and modify. $counterA and $counterB never interfere with each other because they closed over two separate $count instances.
A multiplier generator
The same shape works for building specialized functions out of a general one — a common technique sometimes called "partial application." Here, makeMultiplier bakes a fixed factor into a fresh closure each time it's called.
Generating specialized functions
<?php
function makeMultiplier($factor) {
return function ($value) use ($factor) {
return $value * $factor;
};
}
$double = makeMultiplier(2);
$triple = makeMultiplier(3);
echo $double(21), "\n";
echo $triple(21), "\n";42 63
Binding $this into a closure
Closures created inside an instance method automatically bind
$this to that instance, but closures created outside a class — or
detached ones — do not have a $this at all. Closure::bind() (a
static method) and ->bindTo() (an instance method) let you attach
or re-attach an object context to an existing closure, which is
occasionally handy for building small dependency-free plugins or
DSL-style helpers.
Attaching an object context with bindTo()
<?php
class Cart {
private array $items = ['pen', 'notebook'];
}
$describe = function () {
return 'Items: ' . implode(', ', $this->items);
};
$bound = Closure::bind($describe, new Cart(), Cart::class);
echo $bound();Items: pen, notebook
Notice the closure could reach $this->items even though $items
is private — the third argument to Closure::bind() tells PHP
which class's scope to evaluate visibility rules against, letting
the bound closure see private and protected members as if it were
defined inside that class.
A classic loop-and-reference-capture pitfall
<?php
$callbacks = [];
for ($i = 1; $i <= 3; $i++) {
$callbacks[] = function () use (&$i) {
return $i;
};
}
foreach ($callbacks as $callback) {
echo $callback(), "\n"; // NOT 1, 2, 3
}4 4 4
All three closures share the same $i, which the for loop leaves at 4 after it exits. Capturing use ($i) by value instead would have frozen each closure's own copy at the moment it was created, producing 1, 2, 3 as most people expect.
use ($var)copies the value at closure-creation time; later changes to the outer variable are invisible to the closure.use (&$var)shares the same storage location; changes on either side are visible on both.A closure returned from a factory function keeps its captured variables alive for as long as the closure itself exists — this is how counter and multiplier generators work.
Closure::bind()and->bindTo()attach an object (and optionally a class scope) to a closure that did not originally have$this.