First Class Callable Syntax (PHP 8.1)
debt(d5/e1/b1/t3)
Closest to 'specialist tool catches it' (d5). The detection_hints list rector and phpstan as the tools that identify old-style Closure::fromCallable() or string callables where first-class callable syntax would be cleaner. These are specialist static analysis / refactoring tools, not default linters, matching the d5 anchor.
Closest to 'one-line patch or single-call swap' (e1). The quick_fix explicitly states replacing string callables and Closure::fromCallable() with the ... syntax — this is a mechanical one-call substitution at each site, and Rector can automate it entirely.
Closest to 'minimal commitment' (b1). This is a syntactic preference choice localised to individual call sites. Choosing not to use first-class callables imposes negligible structural weight — it doesn't shape architecture or slow down other work streams.
Closest to 'minor surprise' (t3). The misconception field identifies a common wrong belief — that Closure::fromCallable() and first-class callables behave differently at runtime — but this is a minor surprise rather than a serious trap. The two produce identical Closure objects, and the difference is only syntactic. A competent developer might be briefly surprised but the correction is straightforward.
Also Known As
TL;DR
Explanation
PHP 8.1 introduced the first class callable syntax: $fn = strlen(...) creates a Closure bound to strlen. This works with functions, static methods, instance methods, and built-in functions. It replaces the verbose Closure::fromCallable('strlen') pattern and avoids string-based callables which are not type-safe and don't survive renaming refactors. First class callables compose well with array_map(), usort(), and other higher-order functions.
Common Misconception
Why It Matters
Common Mistakes
- Still using Closure::fromCallable('method') when the first-class callable syntax is available and cleaner.
- Using string callables ('strlen') where a first-class callable would allow IDE to verify the function exists.
- Not realising that the syntax works for static methods (Foo::bar(...)), instance methods ($obj->method(...)), and built-ins.
- Using it where a simple anonymous function is clearer — fn($x) => transform($x) vs transform(...).
Code Examples
// String callable — no IDE support, no refactoring safety:
$fn = Closure::fromCallable('array_reverse');
$lengths = array_map('strlen', $strings); // Refactor 'strlen'? IDE won't catch
// First-class callable:
$fn = array_reverse(...);
$lengths = array_map(strlen(...), $strings);
// PHP 8.1 first-class callable syntax — wrap any callable as a Closure
// Before: cumbersome
$fn = Closure::fromCallable('strlen');
$fn = fn($s) => strlen($s);
// After: clean and composable
$fn = strlen(...);
// Works on static methods, instance methods, built-ins
$sort = usort(...);
$trim = trim(...);
$getAge = $user->getAge(...);
$create = User::create(...);
// Compose with array functions — no more fn($x) => wrapper
$lengths = array_map(strlen(...), $strings);
$sorted = usort($items, strcmp(...));
// Pass as argument
function transform(array $items, callable $fn): array {
return array_map($fn, $items);
}
$upper = transform($words, strtoupper(...));