A first-class callable is the strlen(...) syntax that turns a function or method into a Closure. An earlier RFC allowed closures in constant expressions, like attribute arguments and property defaults, but it left first-class callables out. This RFC from Tim Düsterhus and Volker Dusch fills that gap.

How it works

You can now pass a first-class callable directly into an attribute:

#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
class Attr {
    public function __construct(public Closure $value) {}
}

#[Attr(self::myMethod(...))]
#[Attr(strrev(...))]
class C {
    private static function myMethod(string $foo) {
        return "XXX";
    }
}

foreach ((new ReflectionClass(C::class))->getAttributes() as $reflectionAttribute) {
    $closure = $reflectionAttribute->newInstance()->value;
    var_dump($closure('abc'));
}

// Prints:
//
// string(3) "XXX"
// string(3) "cba"

It follows the rules first-class callables and constant expressions already use:

  • If the class isn't loaded yet, the autoloader runs.
  • Inside a namespace, PHP looks for the function there first, then falls back to the global namespace.
  • Scope rules apply, so a callable in an attribute or property default can point to a private method of the surrounding class, like self::myMethod(...) above.

There are some limits:

  • Only plain functions and static methods (::) are supported.
  • The name has to be written out, like strrev(...) or ClassName::method(...). Forms like [ClassName::class, 'method'](...) aren't allowed.
  • Methods that only exist through __callStatic() aren't supported.

What it means for existing code

Nothing breaks. This code used to be a compile-time error, so no working code relies on it. Static analysis tools and IDEs need updates so they stop flagging it as an error.

The vote

The RFC passed 29 to 0, clearing the two-thirds majority it needed. Voting closed on February 20, 2025, and the feature shipped in PHP 8.5.