Closures in Constant Expressions
PHP 8.5 allows static closures and first-class callables in constant expressions — meaning you can use them as default parameter values, in attribute arguments, and as class constant values. This unlocks patterns that were previously impossible.
// Closures as default parameter values
function process(
array $data,
Closure $transform = static fn($x) => $x, // Identity function default
): array {
return array_map($transform, $data);
}
process([1, 2, 3]); // [1, 2, 3] — default identity
process([1, 2, 3], fn($n) => $n * 2); // [2, 4, 6]
// First-class callables in attribute arguments
#[Attribute]
class Validator {
public function __construct(public Closure $rule) {}
}
class UserForm {
#[Validator(is_string(...))]
public string $name;
#[Validator(static fn($v) => strlen($v) >= 8)]
public string $password;
}
// Closures as class constants
class Transforms {
const Closure NORMALIZE = static fn(string $s) => strtolower(trim($s));
const Closure SLUGIFY = static fn(string $s) =>
preg_replace('/[^a-z0-9]+/', '-', strtolower(trim($s)));
}
This was one of the last restrictions on where closures could appear in PHP. Removing it makes callback-heavy APIs much cleaner — no more "pass null and we'll use a default callback" patterns.
Significance: Composability
When closures can appear anywhere a value can, functional patterns become first-class citizens in PHP's type system. Default callback parameters, strategy constants, and attribute-based configuration all become more natural and expressive.