Closures and Callables

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against PHP 8.5, checked on 2026-10-05; the partial function application examples were run on the PHP 8.6 release candidate and are marked upcoming in 8.6. Functions are values in PHP: they can be stored in variables, passed to other functions and returned from them. This page covers the ways to create such function values and to call them.

Anonymous functions and use

An anonymous function (closure) is a function without a name, written as an expression. It is an instance of the final class Closure. A closure does not see the variables of the scope where it is created unless they are imported with use:

  • use ($x) captures the value of $x at the moment the closure is created;

  • use (&$x) captures a reference, so the closure sees later changes and can modify the variable.

<?php

declare(strict_types=1);

$minYear = 1970;
$isModern = function (array $book) use ($minYear): bool {
    return $book['year'] >= $minYear;
};

$minYear = 2000;            // too late: the closure captured 1970

$books = [['title' => 'Solaris', 'year' => 1961], ['title' => 'Kindred', 'year' => 1979]];
echo implode(', ', array_column(array_filter($books, $isModern), 'title')), "\n";   // Kindred

// Capturing by reference: a counter shared with the closure
$loans = 0;
$lend = function (string $isbn) use (&$loans): string {
    $loans++;
    return "lent {$isbn}";
};
$lend('978-0-00-000001-1');
$lend('978-0-00-000003-5');
echo $loans, "\n";          // 2

Inside a class method, a closure automatically binds $this (the object the method was called on). Declaring the closure static prevents this, which avoids keeping the object alive through the closure and makes the intent clear; static closures cannot use $this.

Arrow functions

Arrow functions (7.4), fn (params) => expression, are a compact syntax for closures whose body is a single expression. They capture every variable they use from the enclosing scope automatically and by value, at creation time; they cannot capture by reference or modify outer variables. They nest, and inner arrow functions capture through outer ones.

<?php

declare(strict_types=1);

$discount = 0.1;
$prices = [10.0, 20.0];

$discounted = array_map(fn (float $p): float => round($p * (1 - $discount), 2), $prices);
print_r($discounted);                                  // [9, 18]

$x = 1;
$arrow = fn (): int => $x;
$x = 2;
var_dump($arrow());                                    // int(1): captured by value when created

// Nested: the inner function sees $factor through the outer one
$multiplier = fn (int $factor): Closure => fn (int $n): int => $n * $factor;
var_dump($multiplier(3)(14));                          // int(42)

// Since 8.0, throw is an expression, so it can appear in an arrow function
$require = fn (?string $v): string => $v ?? throw new InvalidArgumentException('missing value');

Use arrow functions for short callbacks; use a full closure when the body needs statements, or the outer variable must be modified.

Source: Arrow Functions

The Closure class

Closure objects have methods to control their binding: the object $this refers to, and the scope (the class whose private and protected members the closure can access).

Method Purpose

Closure::bind($closure, $newThis, $newScope) / $closure->bindTo($newThis, $newScope)

return a copy bound to another object and/or class scope

$closure->call($newThis, ...$args)

bind temporarily and call in one step (7.0)

Closure::fromCallable($callable)

turn any callable into a Closure (superseded by first-class callable syntax)

Closure::getCurrent()

the currently executing closure, for recursion without a variable (8.5)

<?php

declare(strict_types=1);

final class Shelf
{
    private array $books = ['Solaris', 'Kindred'];
}

// Read a private property by binding a closure to the object's scope (useful in tests and hydrators)
$peek = fn (): array => $this->books;
$bound = Closure::bind($peek, new Shelf(), Shelf::class);
print_r($bound());

$count = function (): int { return count($this->books); };
var_dump($count->call(new Shelf()));          // int(2)

// 8.5: recursion inside an anonymous function without capturing itself
$factorial = function (int $n): int {
    return $n <= 1 ? 1 : $n * Closure::getCurrent()($n - 1);
};
var_dump($factorial(5));                      // int(120)

Binding closures into other classes' scopes bypasses encapsulation; reserve it for infrastructure code (serialisers, test helpers, frameworks). Since 8.5, binding misuses that used to warn — binding an object to a static closure, unbinding $this from a closure that uses it, rebinding the scope of a closure created from a method — are deprecated.

First-class callable syntax (PHP 8.1)

Writing a call with ... as its only argument, strlen(...), creates a Closure from any function or method without calling it. It works for functions, methods, static methods and invokable objects, respects scope (a private method can be turned into a closure from inside its class) and is checked when written: a typo is an Error immediately rather than a confusing failure later.

<?php

declare(strict_types=1);

final class Catalogue
{
    private array $titles = ['solaris', 'kindred'];

    public function titles(): array
    {
        return array_map($this->format(...), $this->titles);   // private method as a callback
    }

    private function format(string $title): string
    {
        return ucfirst($title);
    }

    public static function slug(string $title): string
    {
        return strtolower(str_replace(' ', '-', $title));
    }
}

$upper = strtoupper(...);
echo $upper('kindred'), "\n";                                    // KINDRED

print_r((new Catalogue())->titles());                            // ["Solaris", "Kindred"]
print_r(array_map(Catalogue::slug(...), ['Wild Seed', 'Dawn']));   // ["wild-seed", "dawn"]

var_dump(strlen(...) instanceof Closure);                        // bool(true)

Compared with the string and array forms ('strtoupper', [$this, 'format']), first-class callables are refactoring safe, visible to static analysis, and work for private methods passed out of the class.

The callable type and the callable forms

The callable type accepts every value PHP can call:

Form Example

a Closure (anonymous, arrow, first-class callable)

fn ($x) => $x * 2, strlen(...)

an object with __invoke()

new Slugger()

a function name string

'strtoupper', 'App\format'

a static method string

'Catalogue::slug'

an [object, 'method'] array

[$catalogue, 'titles']

a [class, 'method'] array

[Catalogue::class, 'slug']

All forms are called the same way, $callable(...$args); call_user_func() and call_user_func_array() are no longer needed. is_callable() checks a value, optionally reporting the callable’s name. The callable type cannot be used for properties and cannot express a signature; a property or parameter of type Closure, documented in PHPDoc as Closure(string): int, is often the better choice.

<?php

declare(strict_types=1);

final class Slugger
{
    public function __invoke(string $s): string { return strtolower(str_replace(' ', '-', $s)); }
}

/** @param callable(string): string $transform */
function applyToTitles(array $titles, callable $transform): array
{
    return array_map($transform, $titles);
}

$titles = ['Wild Seed', 'Dawn'];
print_r(applyToTitles($titles, new Slugger()));
print_r(applyToTitles($titles, 'strrev'));
var_dump(is_callable([new Slugger(), '__invoke'], false, $name), $name);   // bool(true) "Slugger::__invoke"

Closures in constant expressions (PHP 8.5)

Since PHP 8.5, static closures and first-class callables can appear in constant expressions: global and class constants, parameter defaults, property defaults, static variable initialisers and attribute arguments. This lets attributes and configuration constants carry behaviour, for example a validation rule or a default formatter.

<?php

declare(strict_types=1);

const UPPER = static function (string $s): string { return strtoupper($s); };

final class Formats
{
    public const LENGTH = strlen(...);       // first-class callable in a class constant
}

function render(string $title, Closure $format = UPPER): string   // closure as a parameter default
{
    return $format($title);
}

echo render('kindred'), ' ', (Formats::LENGTH)('kindred'), "\n";   // KINDRED 7

Closures in constant expressions must be static (they cannot use $this) and cannot capture variables with use.

Composing functions with the pipe operator

The pipe operator |> (8.5) passes a value through a sequence of single-argument callables, which turns function composition into a readable top-to-bottom pipeline (the operator itself is described in Operators and Expressions). Combined with closures that return closures, it builds reusable steps:

<?php

declare(strict_types=1);

$keepFrom = fn (int $year): Closure => fn (array $books): array =>
    array_filter($books, fn (array $b): bool => $b['year'] >= $year);
$sortBy = fn (string $key): Closure => function (array $books) use ($key): array {
    usort($books, fn (array $a, array $b): int => $a[$key] <=> $b[$key]);
    return $books;
};
$titles = fn (array $books): array => array_column($books, 'title');

$books = [
    ['title' => 'Imago', 'year' => 1989],
    ['title' => 'Solaris', 'year' => 1961],
    ['title' => 'Kindred', 'year' => 1979],
];

$result = $books
    |> $keepFrom(1970)
    |> $sortBy('year')
    |> $titles
    |> (fn (array $t): string => implode(', ', $t));

echo $result, "\n";     // Kindred, Imago

Partial function application (upcoming in PHP 8.6)

PHP 8.6 adds partial function application: calling a function with ? placeholders (and optionally ... for "all remaining parameters") does not call it, but returns a Closure that takes the missing arguments. It removes most of the small arrow functions that the pipe operator otherwise needs. The following was run on PHP 8.6.0RC2:

<?php

declare(strict_types=1);

// requires PHP 8.6
$splitLines = explode("\n", ?);                   // Closure(string): array
print_r($splitLines("Solaris\nKindred"));

$pad = str_pad(?, 10, '.');                        // fix every argument except the first
echo $pad('Dawn'), "|\n";                          // Dawn......|

$replaceIn = str_replace(?, ?, 'The Left Hand of Darkness');
echo $replaceIn('Darkness', 'Night'), "\n";        // The Left Hand of Night

// With the pipe operator: no wrapping arrow functions needed
echo "Solaris\nKindred\nDawn" |> explode("\n", ?) |> count(...), "\n";   // 3

Until 8.6 is released (scheduled for 2026-11-19) and adopted, write fn (string $s): array => explode("\n", $s).

Functional idioms over arrays and iterables

PHP’s array functions take callbacks first (array_map, usort) or last (array_filter, array_reduce), a historical inconsistency that named arguments help with. For large or streaming data, generators give lazy versions of map and filter that never build intermediate arrays (see Iterators and Generators):

<?php

declare(strict_types=1);

/** @template T @param iterable<T> $items @param callable(T): bool $keep @return Generator<T> */
function lazyFilter(iterable $items, callable $keep): Generator
{
    foreach ($items as $key => $item) {
        if ($keep($item)) {
            yield $key => $item;
        }
    }
}

/** @param iterable<mixed> $items */
function lazyMap(iterable $items, callable $fn): Generator
{
    foreach ($items as $key => $item) {
        yield $key => $fn($item);
    }
}

$isbns = (function (): Generator {          // imagine millions of lines from a file
    yield '978-0-00-000001-1';
    yield '979-0-00-000009-9';
    yield '978-0-00-000003-5';
})();

$result = lazyMap(
    lazyFilter($isbns, fn (string $i): bool => str_starts_with($i, '978')),
    fn (string $i): string => str_replace('-', '', $i),     // PHP 8.6: str_replace('-', '', ?)
);

foreach ($result as $isbn) {
    echo $isbn, "\n";
}

A first-class callable (str_replace(...)) cannot pre-fill arguments, which is why the mapping step is an arrow function; partial application in PHP 8.6 will shorten it to str_replace('-', '', ?).

Common pitfalls

  • use captures by value at creation: later changes to the variable are not seen, unless & is used.

  • Arrow functions cannot modify outer variables: assignments inside them change a local copy.

  • Non-static closures in classes keep $this alive: mark long-lived callbacks static.

  • String callables are not checked until called: 'strtoupperr' fails at runtime; strtoupperr(...) fails where it is written.

  • Arrow functions in pipes need parentheses: $x |> (fn ($v) => …).

Where older books and tutorials are outdated

  • create_function() (Beak ch. 2, Kromann ch. 4) was deprecated in 7.2 and removed in 8.0; it compiled code from a string, like eval().

  • Arrow functions (7.4), first-class callables (8.1), closures in constant expressions, Closure::getCurrent() and the pipe operator (8.5), and partial application (8.6) are absent from books for PHP 7.3 and older.

  • call_user_func() everywhere — $callable(...$args) works for every callable form.

  • Closure::fromCallable() — superseded by foo(...).

  • The IIFE idiom (function () { … })(); still works and is sometimes used to create a private scope; in most cases a named function or a class method is clearer.