ZetCode

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.

basic.php
<?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.

use_value.php
<?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.

callbacks.php
<?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.

factory.php
<?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.

arrow.php
<?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.

this_binding.php
<?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.

bind.php
<?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.

first_class.php
<?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.

memoize.php
<?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.

event.php
<?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

PHP Closure class

PHP anonymous functions

First-class callable syntax

In this article, we have covered PHP closures. See also the PHP arrow functions, PHP function and PHP variable scope tutorials.

Author

My name is Jan Bodnar and I am a passionate programmer with many years of programming experience. I have been writing programming articles since 2007. So far, I have written over 1400 articles and 8 e-books. I have over eight years of experience in teaching programming.

List all PHP tutorials.