PHP Closures and Variable Capture with use
What closures (anonymous functions) are in PHP, how variable capture with the use keyword works, and practical everyday examples.
Closures (anonymous functions) were introduced in PHP 5.3 — functions that can be assigned to a variable or passed as an argument to another function. Over time they became an indispensable part of my day-to-day PHP code. The more I used them for array operations and anywhere a callback was needed, the more I appreciated how practical they are.
That said, understanding closures alone is not enough; you also need to understand variable capture with use. Anyone who skips that part will struggle to figure out why an expected value is not visible inside the function.
What is a closure?
In its simplest form: a function with no name that can be assigned to a variable.
$greet = function (string $name): string {
return 'Hello, ' . $name;
};
echo $greet('Ahmet'); // Hello, Ahmet
Here $greet holds a Closure object. You can pass it to another function:
$names = ['Ahmet', 'Mehmet', 'Ayşe'];
$greeted = array_map(function (string $name) {
return 'Hello, ' . $name;
}, $names);
Built-in functions like array_map, array_filter, and usort all accept a callback parameter — closures fit that role naturally.
Variable capture with use
A closure does not automatically have access to variables in the surrounding scope. PHP behaves differently from JavaScript here. To use an outer variable inside a closure you must explicitly capture it with the use keyword.
$discount = 0.10;
$applyDiscount = function (float $price) use ($discount): float {
return $price * (1 - $discount);
};
echo $applyDiscount(100); // 90
Without use ($discount), $discount would trigger an undefined variable error.
Once you understand why this design choice was made, it makes sense. PHP was designed to keep scope under control. If a closure could silently access every surrounding variable, tracking which external data a large function depended on would become a nightmare. The use list explicitly documents a closure’s dependencies on the outside world.
Capture by value vs. capture by reference
The default behaviour is by-value capture: the closure takes a snapshot of the variable’s value at definition time, and later changes to that variable have no effect on the closure.
$multiplier = 2;
$double = function (int $n) use ($multiplier): int {
return $n * $multiplier;
};
$multiplier = 5; // This change does NOT affect $double
echo $double(10); // 20, because the value at definition time (2) is used
To capture by reference, write use (&$multiplier). This is needed less often — typically when you want the closure to mutate state in the outer scope.
The classic use case for by-reference capture is a counter or accumulator pattern:
$count = 0;
$increment = function () use (&$count): void {
$count++;
};
$increment();
$increment();
echo $count; // 2
Use this pattern with care, though. A closure that mutates external state makes side effects harder to track. Returning a value and accumulating it outside is usually more readable.
A real-world example: filtering arrays
In practice, the place I reach for this pattern most often is dynamic filtering — a filter criterion comes from outside and is captured inside the closure:
function filterByStatus(array $orders, string $status): array
{
return array_filter($orders, function (array $order) use ($status): bool {
return $order['status'] === $status;
});
}
$pendingOrders = filterByStatus($orders, 'pending');
Without capturing $status via use, accessing $status inside the closure would be impossible. This structure is both readable and reusable.
You can easily extend the same idea to multiple criteria:
function filterOrders(array $orders, string $status, ?string $city = null): array
{
return array_filter($orders, function (array $order) use ($status, $city): bool {
if ($order['status'] !== $status) {
return false;
}
if ($city !== null && $order['city'] !== $city) {
return false;
}
return true;
});
}
Multiple variables can appear in the use list, separated by commas.
Passing a closure as a function parameter
Functions you write yourself can also accept a closure as a parameter. Use callable or Closure as the type hint:
function applyToEach(array $items, Closure $fn): array
{
$result = [];
foreach ($items as $key => $item) {
$result[$key] = $fn($item);
}
return $result;
}
$prices = [100, 200, 150];
$discounted = applyToEach($prices, function (float $price) {
return $price * 0.9;
});
This structure lets you abstract the operation. The looping logic lives inside applyToEach; the transformation logic is injected from outside.
The difference between callable and Closure: callable also accepts string function names and [$object, 'method'] arrays, while Closure only accepts anonymous functions. Closure is more restrictive from a type-safety standpoint, but if you intentionally expect only an anonymous function it expresses intent more clearly.
PHP 7.4 and later: short closure syntax
Arrow functions, introduced in PHP 7.4, shortened the closure syntax considerably. For single-expression closures you use the fn keyword and drop use entirely — the outer scope is captured automatically:
$discount = 0.10;
$applyDiscount = fn(float $price): float => $price * (1 - $discount);
When this post was originally written (2017), arrow functions did not yet exist in the language. Today this syntax is more common for simple single-expression closures. The use-based capture remains valid and necessary for multi-line closures.
The biggest pitfall when learning closures is skipping over the variable-capture mechanism entirely. Once you understand it, closures make array operations, event listeners, and anything else that needs a callback shorter and more intentional.
Comments
Sign in with your GitHub account to join the discussion. Comments are stored in GitHub Discussions.