First-class Callable Syntax
What Is First-class Callable Syntax?
In simple terms: First-class callable syntax lets you turn any existing function or method into a value you can store and pass around, by writing its name followed by (...). Technically: Introduced in PHP 8.1, the syntax functionName(...) creates a Closure object from a function, method, or static method without calling it. The ... is literal syntax — it does not mean "fill in arguments here".
$toUpper = strtoupper(...); // a Closure wrapping strtoupper()
echo $toUpper('php'); // PHP
Why It Was Added
Before PHP 8.1, functions were passed around as strings or arrays:
array_map('strtoupper', $names);
array_map([$this, 'formatName'], $names);
usort($items, [PriceSorter::class, 'compare']);
These forms have real drawbacks:
- Typos are invisible until runtime:
'strtouper'is just a string. - Tools cannot follow them: IDE "find usages", renaming, and static analysis treat strings as plain text.
- Scope problems: private methods passed as
[$this, 'method']can fail when the callable is later invoked from outside the class.strtoupper(...)and$this->formatName(...)are real language syntax. PHP verifies that the function exists when the closure is created, and tools understand exactly which function is meant.
Syntax
| Callable Target | First-class Callable Syntax |
|---|---|
| Function | strlen(...) |
| Instance method | $object->method(...) |
| Static method | ClassName::method(...) |
| Method on the current object | $this->method(...) |
| Static method of the current class | self::method(...) / static::method(...) |
| Callable stored in a variable | $callable(...) |
Example
<?php
$toUpper = strtoupper(...);
$trim = trim(...);
echo $toUpper('learn php') . "\n";
echo '[' . $trim(' spaced ') . ']' . "\n";
var_dump($toUpper instanceof Closure);
Output
LEARN PHP
[spaced]
bool(true)
The result is an ordinary Closure that calls the original function with whatever arguments it receives.
Using It With Array Functions
<?php
$tags = [' php ', 'MySQL', ' Laravel '];
$cleaned = array_map(trim(...), $tags);
$lowercase = array_map(strtolower(...), $cleaned);
echo implode(', ', $lowercase) . "\n";
Output
php, mysql, laravel
trim(...) reads like a reference to the function itself — much clearer than the string 'trim', and safe from typos.
Methods as Callables
Instance Methods
<?php
$date = new DateTimeImmutable('2026-10-09');
$format = $date->format(...);
echo $format('D, d M Y') . "\n";
echo $format('Y-m-d') . "\n";
Output
Fri, 09 Oct 2026
2026-10-09
$date->format(...) creates a closure bound to $date. Calling it later always formats that specific date.
Static Methods
<?php
$parseDate = DateTimeImmutable::createFromFormat(...);
$deadline = $parseDate('d/m/Y', '15/10/2026');
echo $deadline->format('l, d F Y') . "\n";
Output
Thursday, 15 October 2026
Private Methods Inside a Class
Inside a class, first-class callable syntax can wrap private methods. The closure keeps the class scope, so it works even when another function (such as array_map()) calls it:
<?php
class PriceList
{
public function __construct(private array $prices) {}
public function withGst(): array
{
return array_map($this->addGst(...), $this->prices);
}
private function addGst(float $price): float
{
return round($price * 1.18, 2);
}
}
$list = new PriceList([100, 250, 499]);
echo implode(', ', $list->withGst()) . "\n";
Output
118, 295, 588.82
addGst() stays private — it is an internal detail of PriceList — yet it can be used as a callback inside the class. Classes are covered in the PHP OOP section.
Comparison With Other Ways to Create Callables
| Technique | Example | Checked When Created? | Understood by Tools? |
|---|---|---|---|
| String | 'strtoupper' | No | Limited |
| Array | [$obj, 'format'] | No | Limited |
| Closure wrapper | fn($s) => strtoupper($s) | Yes | Yes |
Closure::fromCallable() | Closure::fromCallable('strtoupper') | Yes | Limited |
| First-class callable | strtoupper(...) | Yes | Yes |
Errors Appear Immediately
<?php
try {
$fn = strtouper(...); // typo
} catch (Error $e) {
echo get_class($e) . ": " . $e->getMessage() . "\n";
}
Output
Error: Call to undefined function strtouper()
With the string 'strtouper', the mistake would surface only when the callback is finally called — possibly in a different part of the program. With first-class callable syntax, it fails at the line where the typo is.
It Is Not Partial Application
The ... does not allow fixing some arguments in advance. These are not valid:
str_replace('a', 'b', ...); // parse error
strtoupper('x', ...); // parse error
To pre-fill arguments, use an arrow function:
<?php
$replaceSpaces = fn(string $text): string => str_replace(' ', '-', $text);
echo $replaceSpaces('first class callable') . "\n";
Output
first-class-callable
Other Restrictions
new ClassName(...)is not allowed ("Cannot create Closure for new expression"). Wrap object creation in an arrow function:fn(...$args) => new ClassName(...$args).- The nullsafe operator cannot be combined with it:
$object?->method(...)is a compile-time error.
First-class Callables and the Pipe Operator (PHP 8.5+)
The pipe operator |> passes a value to a single-argument callable. First-class callables are the natural way to name each step:
<?php
$title = ' First-Class Callables in PHP ';
$result = $title
|> trim(...)
|> strtolower(...)
|> ucwords(...);
echo $result;
Output
First-class Callables In Php
This requires PHP 8.5.
Practical Example: A Validation Rule Table
<?php
function isValidEmail(string $value): bool
{
return filter_var($value, FILTER_VALIDATE_EMAIL) !== false;
}
function isValidPincode(string $value): bool
{
return preg_match('/^[1-9][0-9]{5}$/', $value) === 1;
}
$rules = [
'email' => isValidEmail(...),
'pincode' => isValidPincode(...),
'name' => fn(string $value): bool => trim($value) !== '',
];
$input = [
'name' => 'Meena',
'email' => 'meena@example',
'pincode' => '600042',
];
foreach ($rules as $field => $rule) {
$result = $rule($input[$field] ?? '') ? 'valid' : 'invalid';
echo "$field: $result\n";
}
Output
email: invalid
pincode: valid
name: valid
Step-by-Step Explanation
- Two validators are ordinary named functions; first-class callable syntax turns them into closures stored in an array.
- A short rule that needs no name is written as an arrow function. Both kinds of closures can live in the same table.
- The loop applies each rule to the matching input value.
'meena@example'fails because PHP's email validator requires a domain with a dot. - Adding a rule means adding one entry. If a function name is mistyped, PHP reports it as soon as the
$rulesarray is built.
Common Mistakes
Thinking ... Calls the Function
Effect: $result = strtoupper(...); stores a closure, not an uppercase string. Fix: Call the closure: $toUpper = strtoupper(...); $result = $toUpper('text');.
Trying to Pre-fill Arguments
Effect: Parse error. Fix: Use an arrow function to fix some arguments.
Using It With Language Constructs
Effect: echo(...), isset(...), and empty(...) are not functions and cannot be turned into callables. Fix: Wrap them: fn(mixed $v): bool => empty($v).
Requiring It on Older PHP Versions
Effect: Parse error before PHP 8.1. Fix: Use Closure::fromCallable() or arrow functions when supporting PHP 8.0 or earlier.
Best Practices
- Prefer
functionName(...)over string and array callables in PHP 8.1+ code. - Use
$this->method(...)to pass private helper methods as callbacks within a class. - Use arrow functions when arguments must be pre-filled or when the logic is a short custom expression.
- Keep callables in rule tables or dispatch tables to replace long conditional chains. Official reference: https://www.php.net/manual/en/functions.first_class_callable_syntax.php