Closure Recap
A closure is an anonymous function that captures variables from its surrounding scope. PHP closures are objects of class Closure. The Functions tutorial covers basics - this page goes deeper.
The Closure Class
<?php
$cb = function ($x) { return $x * 2; };
var_dump($cb); // object(Closure)
var_dump($cb instanceof Closure); // true
// Closure has static methods - no instance methods you call directly
// Closure::bind, Closure::fromCallable, Closure::call
Closure::bind & bindTo
Rebind $this and the scope of an existing closure:
<?php
class User {
private string $secret = "hidden-value";
public function __construct(public string $name) {}
}
$user = new User("Ruban");
// Reach into private property of $user via a rebound closure
$getSecret = function () {
return $this->secret;
};
$bound = Closure::bind($getSecret, $user, User::class);
echo $bound(); // "hidden-value"
// Equivalent instance method
$bound = $getSecret->bindTo($user, User::class);
// One-off: Closure::call binds and calls in one step
echo $getSecret->call($user); // same result
If you find yourself rebinding closures to access private properties, ask if the class should expose a method instead. Use this only for framework-level concerns: serialisation, lazy proxies, test fixtures.
Closure::fromCallable
Wrap any callable (string, array, first-class method) as a real Closure object:
<?php
// From a function name
$strlen = Closure::fromCallable("strlen");
echo $strlen("hello"); // 5
// From a static method
$decode = Closure::fromCallable([JsonHelper::class, "decode"]);
// From an instance method
$repo = new UserRepo();
$find = Closure::fromCallable([$repo, "find"]);
$user = $find(42);
// PHP 8.1+ - first-class callable syntax is shorter
$find = $repo->find(...); // same effect, modern syntax
static Closures
A static closure does NOT capture $this even inside a method - useful when you want a callable that won\'t accidentally extend object lifetime:
<?php
class Service {
public function buildCallback(): Closure {
// Regular closure - captures $this implicitly
return function ($x) { return $x * 2; };
}
public function buildStaticCallback(): Closure {
// No $this captured - cant call $this->method() inside
return static function ($x) { return $x * 2; };
}
}
// Useful when storing closures long-term (caches, registries) so they
// dont pin the originating object in memory.
Currying & Partial Application
<?php
// Curry: takes a 2-arg function, returns nested 1-arg functions
function curry(callable $fn): Closure {
return fn($a) => fn($b) => $fn($a, $b);
}
$add = fn($a, $b) => $a + $b;
$curriedAdd = curry($add);
echo $curriedAdd(3)(4); // 7
// Partial application - pre-fill some args
function partial(callable $fn, mixed ...$preset): Closure {
return fn(...$args) => $fn(...$preset, ...$args);
}
$greet = fn($greeting, $name) => "$greeting, $name!";
$hello = partial($greet, "Hello");
echo $hello("Ruban"); // "Hello, Ruban!"
echo $hello("Alice"); // "Hello, Alice!"
Memoization
Cache the results of a pure function so repeat calls are free:
<?php
function memoize(callable $fn): Closure {
$cache = [];
return function (...$args) use ($fn, &$cache) {
$key = serialize($args);
return $cache[$key] ??= $fn(...$args);
};
}
// Expensive Fibonacci
$fib = function ($n) use (&$fib) {
return $n < 2 ? $n : $fib($n - 1) + $fib($n - 2);
};
$memFib = memoize($fib);
echo $memFib(30); // first call - computes
echo $memFib(30); // cached - instant
Closures as Callbacks
<?php
// Sort
usort($users, fn($a, $b) => $a->age <=> $b->age);
// Filter
$adults = array_filter($users, fn($u) => $u->age >= 18);
// Map
$names = array_map(fn($u) => $u->name, $users);
// Reduce
$total = array_reduce($cart, fn($acc, $i) => $acc + $i->price, 0);
// Custom event dispatcher
$bus->on("user.registered", function (User $u) use ($mailer, $logger) {
$mailer->welcome($u);
$logger->info("New user: {$u->email}");
});
// HTTP middleware chain
$handler = fn(Request $req) => array_reduce(
array_reverse($middlewares),
fn(Closure $next, callable $mw) => fn($r) => $mw($r, $next),
fn(Request $r) => $finalHandler($r)
)($req);