PHP 8.0: Attributes

Version8.0
TypeNew Feature

One of the biggest new changes in PHP 8 is the Attributes support. Attributes help to add meta-data to PHP functions, parameters, classes, class methods, constants, properties, closures, and even anonymous classes. This meta-data can be fetched programmatically, and provides a pragmatic approach to resolve the attributes in elsewhere in the code.

An in-depth post about the history, different approaches, and practical use cases of Attributes are in the post Attributes in PHP 8.

Syntax and Usage

Attributes are declared with << and >> signs.

<<ExampleAttribute('foo', 'bar')>>
function example() {}

After several suggestions such as @@ and @: for the prefix, the <<Foo>> syntax was chosen due to implementation details in PHP parser.

These tokens will yield a parse error in older versions of PHP, which makes this feature impossible to back-port to older PHP versions.

Attributes can resolve to class names

Although not required, PHP 8 provides functionality to resolve the attribute names to class names. You can use use statements to clean-up the code. Standard rules of class name resolving will be followed.

It is optional to match the Attribute name to a class name.

Attributes can have parameters

Each attribute can have zero or more parameters. They will be passed to the Attribute class constructor if attempted to get an instantiated object of the attribute.

Parameter can be simple scalar types, arrays, or even simple expressions such as mathematical expressions, PHP constants, class constants (including magic constants). Any expression that can be used as a class constant can be used as Attribute parameter.

More than one attribute allowed

Each item that receives Attributes can have zero or many attributes, each in its own <<>> brackets.

Each Attribute can be separated by a white-space (either a new line or a space(s)).

Before and After DocBlock comments

Attributes can appear before and after DocBlock comments. There is no standard recommendation for the code style, but this surely will be ironed out in a future PSR code-style recommendation.

Attribute Syntax Example


use App\Annotations\FooAttribute;
use App\Annotations\ClassAttrib as FooClassAttrib;
use App\Annotations\FooParamAttrib;
use External\Attr\BarClassAttrib;

<<App\Annotations\FooAttribute>>
function foo_func(<<FooParamAttrib('Foo1')>> $foo) {}

<<FooAttribute('hello')>>
<<BarClassAttrib(42)>>
class Foo {
    <<ConstAttr>>
    <<FooAttribute(null)>>
    private const FOO_CONST = 28;
    private const BAR_CONST = 28;

    <<PropAttr(Foo::BAR_CONST, 'string')>>
    private string $foo;

    <<SomeoneElse\FooMethodAttrib>>
    public function getFoo(<<FooClassAttrib(28)>>): string{}
}

// Declare Attributes

/*
 * Attributes are declared with `<<PhpAttribute>>` attribute. So meta!
 */

namespace App\Annotations;

<<PhpAttribute>>
class FooAttribute {
    public function __construct(?string $param1 = null) {}
}

<<PhpAttribute>>
class ClassAttrib {
    public function __construct(int $index) {}
}

Looking for fine-grained examples ?
Attributes in PHP 8 contains several more examples with edge-cases demonstrated.

Fetching Attributes

Attributes are retrieved using the Reflection API. When PHP engine parses code that contains Attributes, they are stored in internal structures for future use. Opcache support included. It does not execute any code or call the constructors of the attributes unless an instance of the Attribute is requested (see examples below).

Using the Reflection API, the Attributes can be retrieved either as strings that contain the Attribute name (with class names resolved), and its optional arguments.

Reflection API can also instantiate an instance of the Attribute class, with class names resolved, autoloaded, and the optional parameters passed to the class constructor. Failure to instantiate the class will throw \Error exceptions that can be caught at the caller level.

New Reflection*::getAttributes() method

$reflector = new \ReflectionClass(Foo::class);
$reflector->getAttributes();

All Reflection* classes get a new method getAttributes method, that returns an array of ReflectionAttribute objects. A synopsis of this new method would be similar to the following:

/**
 *  @param string $name Name of the class to filter the return list
 *  @param int $flags Flags to pass for the filtering process.
 *  @return array ReflectionAttribute[]
 */
public function getAttributes(?string $name = null, int $flags = 0): array {}

ReflectionAttribute class synopsis

final class ReflectionAttribute {
    /**
     * @return string The name of the attribute, with class names resolved.
     */
    public function getName(): string {}

    /**
     * @return array Arguments passed to the attribute when it is declared.
     */
    public function getArguments(): array {}

    /**
     * @return object An instantiated class object of the name, with arguments passed to the constructor.
     */
    public function newInstance(): object {}
}

Attribute filtering

Reflection*::getAttributes() optionally accepts a string of class name that can be used to filter the return array of attributes by a certain Attribute name.

$attrs = $reflector->getAttributes(FooAttribute::class);

$attrs array would now be only ReflectionAttribute objects or FooAttribute Attribute name.

A second optional parameter accepts an integer to further fine tune the return array.

$attrs = $reflector->getAttributes(BaseAttribute::class, \ReflectionAttribute::IS_INSTANCEOF);

At the moment, only \ReflectionAttribute::IS_INSTANCEOF is available.

If \ReflectionAttribute::IS_INSTANCEOF is passed, the return array will contain Attribute with same class name or classes that extends or implements the provided name (i.e all classes that fulfull instanceOf $name).

Retrieving Attribute Objects

ReflectionAttribute::newInstance method returns an instance of the Attribute class, with any parameters passed to the Attribute object class constructor.

A complete example


use My\Attributes\ExampleAttribute;

<<ExampleAttribute('Hello world', 42)>>
class Foo {}

<<PhpAttribute>>
class ExampleAttribute {
    private string $message;
    private int $answer;
    public function __construct(string $message, int $answer) {
        $this->message = $message;
        $this->answer = $answer;
    }
}

$reflector = new \ReflectionClass(Foo::class);
$attrs = $reflector->getAttributes();

foreach ($attrs as $attriubute) {

    $attribute->getName(); // "My\Attributes\ExampleAttribute"
    $attribute->getArguments(); // ["Hello world", 42]
    $attribute->newInstance();
        // object(ExampleAttribute)#1 (2) {
        //  ["message":"Foo":private]=> string(11) "Hello World"        
        //  ["answer":"Foo":private]=> int(42) 
        // }
}

Read in-depth: This is a short summary of the new Attributes feature in PHP 8. For a detailed guide, see Attributes in PHP 8


Backwards Compatibility Impact

Due to the syntax change in Attributes, any code that uses Attributes will not work in older PHP versions.

Attempting to do so will trigger a parse error:

Parse error: syntax error, unexpected '<<' (T_SL), expecting end of file in ... on line ...

RFC Discussion Implementation