First-class callable syntax — strlen(...) as a callable (PHP 8.1+)
Concept
PHP 8.1 introduced first-class callable syntax (FCS), allowing any callable to be referenced as a proper Closure object using the callable(...) notation with literal ... (the splat with no arguments). Previously, creating a closure from a named function required wrapping it: fn($x) => strlen($x) or Closure::fromCallable('strlen'). With FCS you write strlen(...) and PHP returns an equivalent Closure object — fully typed, immediately usable anywhere a callable is expected.
The resulting closure preserves the original function's signature and type information. Static analysis tools and IDEs can introspect it correctly. This is a significant improvement over string callables ('strlen') and array callables ([$object, 'method']), both of which are opaque to static analysis. The string form 'strlen' is also not refactoring-safe; FCS strlen(...) will produce an error immediately if the function does not exist, whereas the string form fails only at runtime when called.
FCS works uniformly across all callable forms:
| Form | FCS syntax |
|---|---|
| Named function | strlen(...) |
| Static method | Str::of(...) |
| Instance method | $obj->method(...) |
| Built-in | array_map(...) |
| Constructor | Not supported — use fn(...$args) => new Foo(...$args) |
The returned closure binds the object scope at creation time for instance methods. If $obj->method(...) is stored and called later, it calls method on the captured $obj instance.
In combination with higher-order array functions, FCS dramatically reduces boilerplate. array_map(strtolower(...), $strings) is cleaner than the arrow function alternative and identical in performance. Laravel's Collection::map(strtolower(...)) reads like declarative English.
Code Example
<?php
declare(strict_types=1);
// Named function → Closure
$lengthFn = strlen(...);
var_dump($lengthFn); // object(Closure)
echo $lengthFn('hello') . PHP_EOL; // 5
// Use with array_map — no wrapping closure needed
$words = ['Hello', 'World', 'PHP'];
$lower = array_map(strtolower(...), $words);
var_dump($lower); // ['hello', 'world', 'php']
// Static method reference
class MathHelper
{
public static function square(int $n): int
{
return $n ** 2;
}
}
$squareFn = MathHelper::square(...);
$squares = array_map($squareFn, [1, 2, 3, 4, 5]);
var_dump($squares); // [1, 4, 9, 16, 25]
// Instance method reference — captures $this implicitly
class Formatter
{
public function __construct(private readonly string $prefix) {}
public function format(string $value): string
{
return $this->prefix . $value;
}
}
$formatter = new Formatter('[INFO] ');
$formatFn = $formatter->format(...); // captures $formatter
$formatted = array_map($formatFn, ['Request started', 'DB queried']);
var_dump($formatted);
// ['[INFO] Request started', '[INFO] DB queried']
// Pipe-style usage with usort
$names = ['Charlie', 'Alice', 'Bob'];
usort($names, strcmp(...)); // compare(...) as comparator
var_dump($names); // ['Alice', 'Bob', 'Charlie']Interview Q&A
Q: What advantage does first-class callable syntax have over string callables like 'strlen'?
Three concrete advantages. First, safety: strlen(...) causes a compile-time parse error if strlen does not exist; 'strlen' silently carries the wrong string until the runtime call_user_func invocation. Second, static analysis: tools like PHPStan and Psalm understand strlen(...) as a Closure<(string $string): int> with full type information; string callables are opaque. Third, refactoring safety: renaming a method in an IDE will update $obj->newName(...) references but will not find the string 'oldName' in a call_user_func call. The only scenario where you might still use strings is when the callable is determined dynamically at runtime from configuration — and even then, you should wrap it immediately with Closure::fromCallable() for type safety downstream.
Q: How does FCS differ from Closure::fromCallable()?
They produce the same result — a Closure wrapping the target callable — but FCS is syntactic sugar baked into the language grammar, while Closure::fromCallable() is a static factory method that accepts any callable (including dynamically constructed ones like [$object, 'method'] built at runtime). FCS requires a literal, known callable at the point of writing. Closure::fromCallable() is the right tool when the callable is assembled programmatically: $method = 'process'; $closure = Closure::fromCallable([$service, $method]);.
Q: Can you use FCS with constructors, and if not, what is the canonical workaround?
Constructors are not supported by first-class callable syntax. new Foo(...) would be ambiguous with the named argument spread and PHP chose not to allow it. The canonical workaround is an arrow function: $factory = fn(...$args) => new Foo(...$args). In Laravel's container, $concrete bindings that are class name strings are resolved by a similar closure created internally: the container wraps the class name in a closure that calls new $concrete(...$resolved) after building the dependency list via Reflection. If you need a named factory, wrap it: $makeOrder = fn(int $userId, Cart $cart) => new Order($userId, $cart).