Take the State of PHP 2026 Survey →

First-class callable syntax View as Markdown

Implemented (PHP 8.1)
Authors
Nikita Popov nikic@php.net, Joe Watkins krakjoe@php.net
Date
2021-05-20
Target
PHP 8.1
Implementation
https://github.com/php/php-src/pull/7019
Source
wiki.php.net/rfc/first_class_callable_syntax

Vote

Introduce first-class callable syntax as proposed? closed
Yes
44
No
0
44 voters
alcaeus Yes 2021-07-02 13:42
as Yes 2021-07-05 05:58
asgrim Yes 2021-07-14 04:21
bwoebi Yes 2021-07-02 12:19
cmb Yes 2021-07-02 14:20
colinodell Yes 2021-07-07 14:13
daverandom Yes 2021-07-07 00:15
derick Yes 2021-07-05 10:14
dharman Yes 2021-07-02 20:55
galvao Yes 2021-07-08 21:44
ilutov Yes 2021-07-02 10:57
imsop Yes 2021-07-04 18:02
irker Yes 2021-07-04 18:42
jhdxr Yes 2021-07-02 19:45
kalle Yes 2021-07-02 17:39
kguest Yes 2021-07-02 15:47
kk Yes 2021-07-14 08:48
klaussilveira Yes 2021-07-08 19:45
kocsismate Yes 2021-07-02 11:11
lcobucci Yes 2021-07-05 06:02
levim Yes 2021-07-02 20:23
marandall Yes 2021-07-02 12:10
marcio Yes 2021-07-07 03:29
nicolasgrekas Yes 2021-07-02 12:05
nikic Yes 2021-07-02 10:43
ocramius Yes 2021-07-02 10:53
patrickallaert Yes 2021-07-04 13:00
petk Yes 2021-07-02 12:22
pollita Yes 2021-07-03 20:49
ralphschindler Yes 2021-07-05 19:41
ramsey Yes 2021-07-06 21:32
rasmus Yes 2021-07-05 18:20
reywob Yes 2021-07-02 15:05
santiagolizardo Yes 2021-07-12 22:32
sergey Yes 2021-07-06 12:31
sirsnyder Yes 2021-07-05 11:01
svpernova09 Yes 2021-07-06 11:40
tandre Yes 2021-07-02 16:02
thekid Yes 2021-07-02 21:00
theodorejb Yes 2021-07-05 18:56
trowski Yes 2021-07-02 12:57
twosee Yes 2021-07-06 08:33
wyrihaximus Yes 2021-07-02 10:56
zimt Yes 2021-07-09 10:46

Discussions on the mailing list

[RFC] First-class callable syntax
by Nikita Popov · 26 messages
[VOTE] First-class callable syntax
by Nikita Popov · 12 messages

RFC text

Copied from wiki.php.net, last synchronized Oct 13, 2026 11:51 UTC

Introduction

This RFC proposes the introduction of a first-class callable syntax, which supersedes existing encodings using strings and arrays. The advantage is that the new syntax is accessible to static analysis, and respects the scope at the point where the callable is created.

$fn = Closure::fromCallable('strlen');
$fn = strlen(...);
 
$fn = Closure::fromCallable([$this, 'method']);
$fn = $this->method(...)
 
$fn = Closure::fromCallable([Foo::class, 'method']);
$fn = Foo::method(...);

In this example, each pair of expressions is equivalent. The strlen(...) syntax creates a Closure that refers to the strlen() function, and so on.

The ... can be seen akin to the argument unpacking syntax ...$args, with the actual arguments not yet filled in:

$fn = Foo::method(...);
// Think of it as:
$fn = fn(...$args) => Foo::method(...$args);

The syntax is forward-compatible with partial functions application.

Proposal

The syntax CallableExpr(...) is used to create a Closure object that refers to CallableExpr with the same semantics as Closure::fromCallable().

CallableExpr can be any expression that can be directly called in the PHP grammar. The following provides a non-exhaustive list of possible syntaxes:

strlen(...);
$closure(...);
$invokableObject(...);
$obj->method(...);
$obj->$methodStr(...);
($obj->property)(...);
Foo::method(...);
$classStr::$methodStr(...);
self::{$complex . $expression}(...);
'strlen'(...);
[$obj, 'method'](...);
[Foo::class, 'method'](...);

Scope

And advantage of the first-class callable syntax is that it uses the scope at the point where the callable is acquired. Consider the following example:

class Test {
    public function getPrivateMethod() {
        return [$this, 'privateMethod']; // does not work
        return Closure::fromCallable([$this, 'privateMethod']); // works, but ugly
        return $this->privateMethod(...); // works
    }
 
    private function privateMethod() {
        echo __METHOD__, "\n";
    }
}
 
$test = new Test;
$privateMethod = $test->getPrivateMethod();
$privateMethod();

If a classical array callable like [$this, 'privateMethod'] is used, then visibility will be checked at the point where the call is performed. If the new $this->privateMethod(...) syntax is used, then the visibility is checked at the point where the callable is created. Closure::fromCallable() already allows you to use those semantics now, with more syntactical boilerplate.

Object creation

The new Foo() syntax is not considered a call, and as such also not supported by the new Foo(...) syntax. It should be noted that there is also no way to express object creation with traditional callable syntax, and it is thus also not supported by Closure::fromCallable().

The general expectation is that new Foo(...) would be creating a new instance of Foo on each call, rather than creating a single instance of Foo and then repeatedly calling the constructor. To support this, it would effectively be necessary to generate a trampoline function of the form

fn(...$args) => new Foo(...$args)

and acquire a callable to that trampoline instead. While certainly possible, this takes a step backwards from the straightforward semantics of the foo(...) syntax for ordinary calls.

Nullsafe calls

The first-class callable syntax cannot be combined with the nullsafe operator. Both of the following result in a compile-time error:

$obj?->method(...);
$obj?->prop->method(...);

The $obj?->method(...) syntax has two potential interpretations:

$fn = $obj?->method(...);
// could be
$fn = $obj !== null ? $obj->method(...) : null;
// or
$fn = fn(...$args) => $obj?->method(...$args);

If this syntax were supported, it would likely follow the first interpretation, as the second interpretation does not correspond to a proper callable. However, this behavior is not particularly useful (as the return type is ?Closure) and likely surprising.

Strict types

The first-class callable syntax interacts with declare(strict_types) the same way as Closure::fromCallable(): The strict_types mode at the point where the callable is acquired does not matter. Only the strict_types mode at the time the call is made is considered.

Rationale

Partial Function Application

This RFC can be seen as an alternative to the partial functions application (PFA) RFC. I believe that PFA use-cases can be divided into roughly three categories:

The first is the use of PFA to acquire a callable, without partially applying any arguments. I believe that the vast majority of PFA uses would be for this purposes. This RFC proposes to provide special support for this use-case only.

The second is use in conjunction with the Pipe Operator (V2) proposal. Taking an example from the RFC:

$result = $var
 |> step_one(?)
 |> step_two(?, 'config')
 |> $obj->stepThree('param', ?);

However, PFA is only required for this particular form of the pipe operator. The Pipe Operator (V1) proposal instead used a syntax that is specific to the pipe operator:

$result = $var
 |> step_one($$)
 |> step_two($$, 'config')
 |> $obj->stepThree('param', $$);

If this definition of the pipe operator is adopted, then PFA is no longer needed for use with the pipe operator. Both approaches to the pipe operator have their advantages. The $$ based variant allows using more than plain function calls in each pipeline step (e.g. you could have $$->getName() as a step, something not possible with PFA), and is also trivially free. A PFA-based optimization would entail significant overhead relative to simple function calls, unless special optimization for the pipe operator usage is introduced (which may not be possible, depending on precise semantics).

Finally, while these two are the primary use-cases of PFA, there will also be the occasional usage in other contexts. For example:

$array = array_filter($array, str_contains(?, 'foo'));

Under this proposal, no dedicated syntax would be available for this use-case, and one would have to use an arrow function:

$array = array_filter($array, fn($s) => str_contains($s, 'foo'));

I think that the existing syntax is already sufficiently concise that there is no strong need to introduce an even shorter one.

As such, I believe that adding a first-class callable syntax, and using the original approach to the pipe operator, would give us most of the benefit of PFA at a much lower complexity cost. The PFA proposal has gone through many iterations, because nailing down the precise semantics turned out to be surprisingly hard. The final proposal is simple on a conceptual level, but very involved when it comes to detailed behavior.

Syntax choice

The proposed syntax is forward-compatible with the latest iteration of the PFA proposal. As such, it would be possible to expand it into a full PFA feature in the future.

The call-based syntax also has the advantage that it is unambiguous: It represents exactly the callable that would be invoked by a direct call of the same syntax. This cannot be said of some other syntax choices that have been discussed in the past, for example:

// Proposed syntax is unambiguous and follows existing semantics:
$this->foo(...);   // Refers to a method
($this->foo)(...); // Refers to a callable stored in a property
 
// What does this mean?
$this->foo::function;

This can be resolved by limiting the ::function syntax to referencing proper symbols only, and not supporting its use to convert legacy callables to closures using $callable::function. Those would require an explicit Closure::fromCallable($callable) call.

A problem with the strlen::function syntax in particular is that it has a false analogy to Foo::class. The latter will just return a string, while the whole point of a first-class callable syntax is that it returns a Closure object, not a simple string or array.

Here are some commonly suggested syntax choices for first-class callables that are definitely not possible due to ambiguities:

// Using "&" sigil:
&foo->bar;
// Is ambiguous with by-reference assignment:
 
$x = &$foo->bar;
// is currently interpreted as
$x =& $foo->bar;
 
// Using no sigil:
strlen; // Is ambiguous with constant strlen
Foo::bar; // Is ambiguous with class constant Foo::bar
$foo->bar; // Is ambiguous with object property $foo->bar

Here are syntax choices that are (mostly) unambiguous if only usage with proper symbols is allowed:

// As mentioned above, people might expect this to return "strlen", not a Closure object:
strlen::function;
// Same as previous, and we'd rather avoid the legacy "callable" terminology.
strlen::callable;
 
// Unlike the "&" sigil, this is not ambiguous. It is also not particularly meaningful though.
*strlen;
// This also applies to various other sigils that are not yet used in unary position, but are equally meaningless:
^strlen;

I am generally open to using a different syntax, as I don't think forward-compatibility with a potential PFA feature is critical, but none of the choices are particularly great.

Backward Incompatible Changes

None.

Vote

Voting started on 2021-07-02 and closes on 2021-07-16.

Vote: Introduce first-class callable syntax as proposed? — Yes: 44 · No: 0 (closed)