PHP Closures
last modified November 14, 2026
In this article, we work with PHP closures. We create anonymous functions,
capture variables with use, return closures from functions, bind
them to objects, and create closures from existing functions with the
first-class callable syntax.
A closure is an anonymous function that can capture variables from the surrounding scope. It is a function that has no name, can be stored in a variable, passed as an argument, and returned from another function.
In PHP, every anonymous function is an object of the built-in
Closure class. The examples were run with PHP 8.5. Short arrow
functions are described separately in the
PHP arrow functions tutorial.
Creating a closure
We create a closure with the function keyword and no name. The
statement ends with a semicolon, because it is an assignment.
<?php
$greet = function (string $name): string {
return "Hello, $name!";
};
echo $greet('Alice'), "\n";
var_dump($greet instanceof Closure);
echo get_class($greet), "\n";
echo (function () {
return 'called immediately';
})(), "\n";
$f = $greet;
echo $f('Bob'), "\n";
The variable $greet holds a Closure object, and we call
it with the usual parentheses. Because the closure is a value, it can also be
copied to another variable, and it can be called right away by wrapping it in
parentheses.
$ php basic.php Hello, Alice! bool(true) Closure called immediately Hello, Bob!
Capturing variables with use
A closure does not see the variables of the enclosing scope automatically. We
must list them in the use clause. By default, they are captured
by value, at the moment the closure is created.
<?php
$rate = 0.2;
$tax = function (float $price) use ($rate): float {
return $price * $rate;
};
echo $tax(100), "\n";
$rate = 0.5;
echo $tax(100), "\n";
$counter = 0;
$byValue = function () use ($counter) {
$counter++;
return $counter;
};
$byRef = function () use (&$counter) {
$counter++;
return $counter;
};
echo $byValue(), $byValue(), "\n";
echo $byRef(), $byRef(), "\n";
echo "counter = $counter\n";
The $tax closure captures the rate 0.2. Changing $rate
afterwards has no effect, so the second call prints the same result.
To let the closure modify the outer variable, we capture it by reference
with &. The $byValue closure increments its own
copy each time it is called, so both calls return 1 and the output is
11. The $byRef closure changes the original variable,
so the calls return 1 and 2 (12), and the final value of
$counter is 2.
$ php use_value.php 20 20 11 12 counter = 2
Closures as callbacks
Many PHP functions take a callback: array_map,
array_filter, array_reduce, usort and
others. Closures are the natural way to supply one.
<?php
$numbers = [5, 3, 8, 1, 9, 2];
$squares = array_map(function (int $n): int {
return $n * $n;
}, $numbers);
echo implode(', ', $squares), "\n";
$min = 4;
$big = array_filter($numbers, function (int $n) use ($min): bool {
return $n > $min;
});
echo implode(', ', $big), "\n";
$sum = array_reduce($numbers, function (int $carry, int $n): int {
return $carry + $n;
}, 0);
echo $sum, "\n";
$users = [
['name' => 'Bob', 'age' => 31],
['name' => 'Alice', 'age' => 25],
['name' => 'Carol', 'age' => 28],
];
usort($users, function (array $a, array $b): int {
return $a['age'] <=> $b['age'];
});
echo implode(', ', array_column($users, 'name')), "\n";
array_map transforms every element, array_filter keeps
the elements for which the callback returns true, and
array_reduce folds the array into a single value. The filter
closure uses use to read the outer variable $min.
The usort function sorts with a custom comparison. The
spaceship operator returns -1, 0 or 1,
which is what the sort needs. The array is sorted by age.
$ php callbacks.php 25, 9, 64, 1, 81, 4 5, 8, 9 28 Alice, Carol, Bob
Returning closures
A function can build and return a closure. The returned closure keeps the captured variables alive, even after the function has finished.
<?php
function multiplier(int $factor): Closure
{
return function (int $n) use ($factor): int {
return $n * $factor;
};
}
$double = multiplier(2);
$triple = multiplier(3);
echo $double(10), "\n";
echo $triple(10), "\n";
function counter(): Closure
{
$count = 0;
return function () use (&$count): int {
return ++$count;
};
}
$c1 = counter();
$c2 = counter();
echo $c1(), $c1(), $c1(), "\n";
echo $c2(), "\n";
function compose(callable ...$fns): Closure
{
return function ($x) use ($fns) {
foreach ($fns as $fn) {
$x = $fn($x);
}
return $x;
};
}
$slug = compose(
'trim',
'strtolower',
fn(string $s) => str_replace(' ', '-', $s)
);
echo $slug(' Hello Closure World '), "\n";
multiplier is a closure factory. Each call creates a new closure
with its own $factor.
counter shows that a closure can carry private state. Each closure
has its own $count, so $c1 counts 1, 2, 3 and
$c2 starts from 1. The state is reachable only through the closure.
compose accepts any number of callables and returns a closure that
passes a value through all of them. We combine the built-in functions
trim and strtolower given as strings with an arrow
function.
$ php factory.php 20 30 123 1 hello-closure-world
Closures and arrow functions
An arrow function fn is a short form of a closure that captures
the whole outer scope automatically, but always by value.
<?php
$factor = 3;
$closure = function (int $n) use ($factor): int {
return $n * $factor;
};
$arrow = fn(int $n): int => $n * $factor;
echo $closure(5), "\n";
echo $arrow(5), "\n";
$total = 0;
$add = fn(int $n) => $total += $n;
echo $add(10), "\n";
echo "total = $total\n";
$multiply = fn(int $a) => fn(int $b) => $a * $b;
echo $multiply(6)(7), "\n";
The arrow function needs no use. On the other hand, it can
neither modify an outer variable nor have a statement body. Here the
assignment $total += $n changes only the arrow function's own copy,
so the outer $total stays 0.
Arrow functions can be nested, which makes currying short, as in the last example.
$ php arrow.php 15 15 10 total = 0 42
Closures and $this
A closure created inside a class method is automatically bound to the current
object, so it can use $this and access private members.
<?php
class Greeter
{
public function __construct(private string $greeting)
{
}
public function makeGreeter(): Closure
{
return function (string $name): string {
return "{$this->greeting}, $name!";
};
}
public function makeStaticGreeter(): Closure
{
return static function (string $name): string {
return isset($this) ? 'has $this' : "no \$this for $name";
};
}
}
$g = new Greeter('Hello');
$fn = $g->makeGreeter();
echo $fn('Alice'), "\n";
$static = $g->makeStaticGreeter();
echo $static('Bob'), "\n";
unset($g);
echo $fn('Carol'), "\n";
The makeGreeter method returns a closure that reads the private
$greeting property through $this. The closure keeps
the object alive; the last call works even after unset($g).
When a closure does not need the object, declare it static. A static
closure does not capture $this, which saves memory and prevents the
object from being kept alive by accident.
$ php this_binding.php Hello, Alice! no $this for Bob Hello, Carol!
Binding closures: bind, bindTo and call
The Closure class has methods that change the object (the
$this) and the class scope of a closure after it was created. This
is a powerful, and dangerous, feature: a bound closure can read private
members.
<?php
class Secret
{
private string $value = 'top secret';
private static int $instances = 7;
}
$peek = function () {
return $this->value;
};
$secret = new Secret();
$bound = Closure::bind($peek, $secret, Secret::class);
echo $bound(), "\n";
$bound2 = $peek->bindTo($secret, $secret);
echo $bound2(), "\n";
echo $peek->call($secret), "\n";
$count = static fn() => static::$instances;
$scoped = Closure::bind($count, null, Secret::class);
echo $scoped(), "\n";
try {
$peek();
} catch (Error $e) {
echo get_class($e), ': ', $e->getMessage(), "\n";
}
Closure::bind and bindTo return a new closure
bound to an object. The third argument of bind and the second of
bindTo set the class scope, which decides what is visible. We pass
the class of the object, so the private property is accessible.
The call method binds the closure temporarily and invokes it in one
step. Passing null as the object and a class as the scope gives
access to static members. Calling the unbound closure fails with an
Error, since there is no $this.
Binding is used by frameworks and testing libraries, for example to build configuration DSLs. In application code, think twice; it breaks encapsulation.
$ php bind.php top secret top secret top secret 7 Error: Using $this when not in object context
First-class callable syntax
Since PHP 8.1, we can create a closure from any callable by writing the call
with ... instead of the arguments. It is shorter than a wrapper
closure and than Closure::fromCallable.
<?php
class MathOps
{
public function square(int $n): int
{
return $n * $n;
}
public static function cube(int $n): int
{
return $n ** 3;
}
}
$len = strlen(...);
echo $len('closure'), "\n";
$ops = new MathOps();
$square = $ops->square(...);
$cube = MathOps::cube(...);
print_r(array_map($square, [1, 2, 3]));
print_r(array_map($cube, [1, 2, 3]));
print_r(array_map(strtoupper(...), ['a', 'b']));
var_dump($len instanceof Closure);
$old = Closure::fromCallable('strrev');
echo $old('abc'), "\n";
strlen(...) creates a closure from a built-in function,
$ops->square(...) from an instance method, and
MathOps::cube(...) from a static method. The result is a real
Closure that can be passed to array_map and similar
functions.
The older Closure::fromCallable does the same for a callable given
as a string or an array.
$ php first_class.php
7
Array
(
[0] => 1
[1] => 4
[2] => 9
)
Array
(
[0] => 1
[1] => 8
[2] => 27
)
Array
(
[0] => A
[1] => B
)
bool(true)
cba
Memoization
Closures can wrap other functions and add behaviour. A classic example is memoization: caching the results of an expensive function.
<?php
function memoize(callable $fn): Closure
{
$cache = [];
return function (...$args) use ($fn, &$cache) {
$key = serialize($args);
if (!array_key_exists($key, $cache)) {
echo "computing for ", implode(',', $args), "\n";
$cache[$key] = $fn(...$args);
}
return $cache[$key];
};
}
$slowSquare = function (int $n): int {
return $n * $n;
};
$fast = memoize($slowSquare);
echo $fast(4), "\n";
echo $fast(4), "\n";
echo $fast(5), "\n";
echo $fast(4), "\n";
The memoize function returns a closure with a private cache,
captured by reference. The key is built from the serialized arguments. The
original function runs only when the arguments are seen for the first time;
the messages computing for ... appear only twice, although we call
the closure four times.
$ php memoize.php computing for 4 16 16 computing for 5 25 16
Event listeners
Closures are a good fit for event-driven code, where the caller supplies the action to take.
<?php
class EventEmitter
{
private array $listeners = [];
public function on(string $event, Closure $listener): void
{
$this->listeners[$event][] = $listener;
}
public function emit(string $event, mixed ...$args): void
{
foreach ($this->listeners[$event] ?? [] as $listener) {
$listener(...$args);
}
}
}
$log = [];
$emitter = new EventEmitter();
$emitter->on('login', function (string $user) use (&$log) {
$log[] = "login: $user";
});
$emitter->on('login', fn(string $user) => print("Welcome, $user!\n"));
$emitter->emit('login', 'alice');
$emitter->emit('login', 'bob');
$emitter->emit('logout', 'alice');
print_r($log);
The EventEmitter class stores listeners per event name. The first
listener captures the $log array by reference and records the
event. The second one is an arrow function that prints a welcome message.
Emitting an event calls every registered listener with the arguments. Emitting
the logout event, which has no listeners, does nothing.
$ php event.php
Welcome, alice!
Welcome, bob!
Array
(
[0] => login: alice
[1] => login: bob
)
Source
In this article, we have covered PHP closures. See also the PHP arrow functions, PHP function and PHP variable scope tutorials.
Author
List all PHP tutorials.