Closures and Callables
In PHP, functions can be values: stored in variables, passed to other functions and returned from them. Anonymous functions (closures), short arrow functions, first-class callable syntax and PHP 8.5's pipe operator make this style concise — and it's what powers array_map, usort, routing tables and event handlers.
Anonymous functions
A function without a name can be assigned to a variable and called through it:
<?php $square = function (int $x): int { return $x * $x; }; echo $square(7), "\n"; echo get_debug_type($square), "\n"; echo implode(", ", array_map($square, [1, 2, 3])), "\n";
49 Closure 1, 4, 9
Note the semicolon after the closing brace — it's an assignment statement.
Capturing variables with use
An anonymous function can't see outside variables unless it lists them in use. By default it captures a copy of the value at the moment the function is created:
<?php $rate = 0.2; $addTax = function (float $price) use ($rate): float { return $price * (1 + $rate); }; $rate = 0.5; // changing it later doesn't affect the closure echo $addTax(100), "\n"; $total = 0; $add = function (int $n) use (&$total): void { // capture by reference $total += $n; }; $add(5); $add(10); echo $total, "\n";
120 15
Arrow functions
For a single expression, an arrow function is much shorter. It captures outside variables automatically (by value) and returns its expression:
<?php $rate = 0.2; $addTax = fn(float $price): float => $price * (1 + $rate); echo $addTax(50), "\n"; $products = [["name" => "Lamp", "price" => 34.5], ["name" => "Desk", "price" => 249.0]]; $minPrice = 100; $expensive = array_filter($products, fn($p) => $p["price"] > $minPrice); echo implode(", ", array_column($expensive, "name")), "\n"; usort($products, fn($a, $b) => $b["price"] <=> $a["price"]); echo $products[0]["name"], "\n";
60 Desk Desk
Arrow functions are the natural choice for callbacks to array functions. Use a full function when you need several statements or want to modify an outside variable.
Callables
Many functions accept a callable: anything PHP can call. That includes closures, function names as strings, and [$object, 'method'] or [ClassName::class, 'method'] arrays:
<?php class Formatter { public function shout(string $s): string { return strtoupper($s) . "!"; } public static function whisper(string $s): string { return strtolower($s) . "..."; } } $words = ["Hello", "World"]; $f = new Formatter(); echo implode(" ", array_map('strrev', $words)), "\n"; echo implode(" ", array_map([$f, 'shout'], $words)), "\n"; echo implode(" ", array_map([Formatter::class, 'whisper'], $words)), "\n"; var_dump(is_callable('strlen'), is_callable('noSuchFunction'));
olleH dlroW HELLO! WORLD! hello... world... bool(true) bool(false)
First-class callable syntax
Since PHP 8.1, writing a function or method call with (...) instead of arguments creates a closure from it. It's clearer than strings and arrays, and editors and static analysis can check it:
<?php class Formatter { public function shout(string $s): string { return strtoupper($s) . "!"; } } $trim = trim(...); $shout = new Formatter()->shout(...); echo "[", $trim(" padded "), "]\n"; echo implode(" ", array_map($shout, ["hi", "there"])), "\n"; echo implode(",", array_map(intval(...), ["1", "2", "3"])), "\n";
[padded] HI! THERE! 1,2,3
(new Formatter()->shout(...) without extra parentheses around new is allowed since PHP 8.4.)
Functions that return functions
A function can build and return a closure. Each returned closure remembers its own captured values:
<?php function multiplier(int $factor): Closure { return fn(int $x): int => $x * $factor; } $double = multiplier(2); $triple = multiplier(3); echo $double(10), " ", $triple(10), "\n"; function makeCounter(): Closure { $count = 0; return function () use (&$count): int { return ++$count; }; } $next = makeCounter(); $next(); $next(); echo $next(), "\n";
20 30 3
The pipe operator (PHP 8.5)
|> passes the value on its left into the callable on its right. Chaining pipes lets you write a sequence of transformations top to bottom, instead of nesting calls inside-out:
<?php $title = " PHP 8.5 Brings The Pipe Operator "; // nested: read from the inside out $slug1 = str_replace(' ', '-', strtolower(trim($title))); // piped: read from top to bottom $slug2 = $title |> trim(...) |> strtolower(...) |> (fn($s) => str_replace(' ', '-', $s)) |> (fn($s) => preg_replace('/[^a-z0-9-]/', '', $s)); echo $slug1, "\n"; echo $slug2, "\n";
php-8.5-brings-the-pipe-operator php-85-brings-the-pipe-operator
Each step must be a callable that takes exactly one argument. Built-in functions with one required parameter work directly via (...); for others, wrap the call in an arrow function — in parentheses, as shown.
Type-hinting callables
Declare parameters as callable (any callable) or Closure (closures only). Here a small event system stores handlers and calls them later:
<?php $handlers = []; function on(array &$handlers, string $event, callable $handler): void { $handlers[$event][] = $handler; } function emit(array $handlers, string $event, mixed $data): void { foreach ($handlers[$event] ?? [] as $handler) { $handler($data); } } on($handlers, 'order.paid', fn($id) => print("Email receipt for order $id\n")); on($handlers, 'order.paid', fn($id) => print("Update stock for order $id\n")); emit($handlers, 'order.paid', 1001); emit($handlers, 'order.cancelled', 1002);
Email receipt for order 1001 Update stock for order 1001
Try it yourself
Build a text-cleaning pipeline from a list of closures and apply it with array_reduce.
[This is messy input]