PHPFirst-Class Callable Syntax

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
<?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
<?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
<?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.

First-class callable syntax doesn't bind arguments
`(...)` produces a closure with the same parameters as the original function or method — it does not let you pre-fill some arguments the way some other languages' partial application does. `substr(...)` produces a closure equivalent to calling `substr($string, $start, $length)`, not a version with any of those arguments already fixed. If you need partial application, you still need to wrap the call in an explicit closure yourself.
  • functionName(...), $object->method(...), and ClassName::staticMethod(...) each produce a real Closure object 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 typed callable or Closure.

  • 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.

Tip
Whenever you catch yourself writing a string- or array-based callable — `'trim'`, `[$this, 'handle']`, `[SomeClass::class, 'method']` — in code that targets PHP 8.1 or later, replace it with the first-class callable syntax (`trim(...)`, `$this->handle(...)`, `SomeClass::method(...)`). It's a drop-in change that costs nothing at runtime and gains you real IDE navigation and refactor safety.