PHP 8.1: First-class Callable Syntax

Version8.1
TypeNew Feature

PHP 8.1 and later supports a new syntax in creating a callable from within the current scope. With this new syntax, it is easier to create a callable using the same syntax a function/method is called, instead of using Closure::fromCallable.

Closure::fromCallable returns a callable (Closure object) from a PHP callable (function name, method, or anonymous function). The first-class callable syntax aims to reduce the boilerplate code of Closure::fromCallable.

For example, the following returns a callable that calls strtoupper:

$callable = Closure::fromCallable('strtoupper');
echo $callable('foo'); // FOO

Using the first-class callable syntax, the same callable can be created as follows:

-$callable = Closure::fromCallable('strtoupper');
+$callable = strtoupper(...);
echo $callable('foo'); // FOO

Syntax

A valid callable followed by (...) makes a first-class callable.

Function names

$callable = strlen(...);

Class Methods

$callable = $item->doSomething(...);

Static Methods

$callable = $item::doSomething(...);

Anonymous Functions

$function = function() {};
$callable = $function(...);

Note that the ... token is not to be used as a placeholder for a specific parameter. Although this syntax allows partial function application, it is not supported at the moment. Attempting to pass any parameters to the callable results in a syntax error:

$callable = str_contains($text, ...);
Parse error: syntax error, unexpected token ")" in ... on line ...

PHP also uses the ... token (internally called T_ELLIPSIS) for variadic function declarations and at call-sites as the spread operator. There are no changes in variadic function declarations and spread operator behavior.

Restrictions

Object Instantiating is not allowed

Instantiating a new object using the new construct is not supported with first-class callable syntax. This behavior is similar to Closure::fromCallable.

$callable = new SplFixedArray(...);
Fatal error: Cannot create Closure for new expression in ... on line ...

Null-safe methods are not allowed

First-class callable syntax does not allow null-safe methods because it cannot be guaranteed to be a callable.

$test?->doSomething(...);
Fatal error: Cannot combine nullsafe operator with Closure creation in ... on line ...

Attributes Arguments

Attributes cannot be declared using first-class callable syntax.

#[Attribute(...)]
class Test {}
Fatal error: Cannot create Closure as attribute argument in ... on line ...

Callable Scope

When a first-class callable is created, it also inherits the scope of the call-site that created the callable.

function shout(): void {
    $value = 'Banana';
    echo $value;
}

$value = 'Apple';
$callable = shout(...);

$callable(); // Banana

Because first-class callables are scoped, it is possible to return a callable that involves calling private methods, as long as it is returned from the object scope.

class Clock {
    public function getClockCallable(): callable {
        return $this->getTime(...);
    }

    private function getTime(): int {
        return time();
    }
}

$clock = new Clock();
$clock_callback = $clock->getClockCallable();
echo $clock_callback();

Notice the Clock::getClockCallable method returns a callable, that calls the private method getTime. The existing syntax of creating a callable using an array does allow calling private methods:

class Clock {
    public function getClockCallable(): callable {
-       return $this->getTime(...);
+       return [$this, 'getTime'];
    }

    private function getTime(): int {
        return time();
    }
}

$clock = new Clock();
$clock_callback = $clock->getClockCallable();
echo $clock_callback();
Fatal error: Uncaught Error: Call to private method Clock::getTime() from global scope in ...:...

Backwards Compatibility Impact

First-class callable syntax is a new syntax introduced in PHP 8.1, and it is not possible to back-port it to older PHP versions. Attempting to do results in a parse error:

Parse error: syntax error, unexpected token ")" in ... on line ...

Applications that require compatibility with older versions can continue to use Closure::fromCallable to create a scoped callable.


RFC Discussion Implementation