First-Class Callable Syntax
PHP has always been able to treat functions as values — passing strlen to array_map(), storing a method reference to call later, wrapping a static method as something you can hand off. But the way you did that before PHP 8.1 relied on plain strings and arrays: 'strlen', [$object, 'methodName'], ['ClassName', 'staticMethod']. Those work, but they're just strings and arrays as far as PHP's parser and your IDE are concerned — nothing verifies at the point you write them that the function or method actually exists, or updates them automatically if you rename it. PHP 8.1's first-class callable syntax fixes that by letting you convert any callable reference into a real Closure object using syntax the engine actually understands.
The old ways of creating a callable
Before 8.1, turning a function or method into something passable as a value meant reaching for one of a few string- or array-based conventions.
Pre-8.1 callable patterns
<?php
class Formatter {
public function upper(string $value): string {
return strtoupper($value);
}
public static function trimIt(string $value): string {
return trim($value);
}
}
$formatter = new Formatter();
$byName = 'strlen';
$instanceMethod = [$formatter, 'upper'];
$staticMethod = ['Formatter', 'trimIt'];
$fromCallable = Closure::fromCallable([$formatter, 'upper']);
echo $byName('hello'), "\n";
echo call_user_func($instanceMethod, 'hi'), "\n";All four of those work at runtime, but every one of them represents the callable as a plain string or array — as far as static analysis is concerned, 'strlen' is just a string that happens to match a function name, with no guarantee it's spelled correctly or that the function still exists.
The first-class callable syntax
PHP 8.1 introduces ... as a special call-site marker: writing a function or method call with (...) as its (only) argument list, instead of actually calling it, produces a Closure that wraps it.
Creating closures with first-class callable syntax
<?php
class Formatter {
public function upper(string $value): string {
return strtoupper($value);
}
public static function trimIt(string $value): string {
return trim($value);
}
}
$formatter = new Formatter();
$byName = strlen(...);
$instanceMethod = $formatter->upper(...);
$staticMethod = Formatter::trimIt(...);
var_dump($byName instanceof Closure);
echo $byName('hello'), "\n";
echo $instanceMethod('hi'), "\n";
echo $staticMethod(' padded '), "\n";bool(true) 5 HI padded
strlen(...) reads almost like an ordinary call to strlen(),
except the ... in place of actual arguments tells PHP "don't
call this, wrap it in a Closure instead." Because it's a real
reference to strlen, $formatter->upper, or
Formatter::trimIt at the point it's written — not a string that
merely happens to match — an IDE can validate that the symbol
exists, jump to its definition, and update the reference
automatically during a rename refactor.
Passing first-class callables around
The resulting Closure behaves exactly like any other closure — it can be passed to functions expecting a callable, such as array_map(), stored in a variable, or returned from a function.
Using a first-class callable with array_map
<?php $names = ['amara', 'diego', 'priya']; $uppercased = array_map(strtoupper(...), $names); print_r($uppercased);
Array
(
[0] => AMARA
[1] => DIEGO
[2] => PRIYA
)Why this replaces Closure::fromCallable()
Closure::fromCallable(), added in PHP 8.0, was itself an
improvement over passing raw strings/arrays around, since at
least the result was a genuine Closure object. But its argument
was still just a string or array under the hood — Closure::fromCallable([$formatter, 'upper'])
gives up the same static-analysis guarantees as
[$formatter, 'upper'] on its own, because the method name
'upper' is still just text to the parser. $formatter->upper(...)
achieves the identical runtime result with a syntax the parser
genuinely understands as a reference to that method, which is why
the first-class callable syntax has mostly superseded
Closure::fromCallable() in code written for PHP 8.1 and later.
functionName(...),$object->method(...), andClassName::staticMethod(...)each produce a realClosureobject referencing that callable.The reference is checked and understood by the parser and IDE tooling, unlike string-based callables such as
'strlen'or[$object, 'method'].The resulting closure can be passed anywhere a callable is accepted, including
array_map(),usort(), and function parameters typedcallableorClosure.First-class callable syntax has largely replaced
Closure::fromCallable()for code targeting PHP 8.1+, since it gives the same result with better tooling support.(...)does not bind or pre-fill any arguments — it only converts the reference itself into a closure.