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 ] braces.

#[ExampleAttribute('foo', 'bar')]
function example() {}

Attributes syntax was initially implemented as <<Attribute>>, followed by @@Attribute, and finally as #[Attribute].

In all PHP versions, # starts a comment for the rest of the line. When an attribute is used in its own line, it will not cause a syntax error in PHP versions prior to 8.0. This makes code that uses PHP 8.0 attributes somewhat backwards compatible, as long as each attribute is attributed in its own line, which makes PHP ignore those attributes.

Attributes may 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.

If an attribute does not map to a class, the Reflection API does not allow to fetch an instance of the instantiated attribute.

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, or inside the same brackets separated by a comma.

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

#[Attr]
#[FooAttr]
function foo(){}

#[Attr, FooAttr]
function bar(){}

By default, the same attribute is not allowed to be used more than once. However, it is possible to declare an attribute that explicitly allows repeated use.

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.

Declaring Attributes

In order to use attributes, the attribute itself may be declared as a class. This is validated only when the attribute is fetched, and not immediately when the code is parsed.

A PHP attribute is a standard PHP class, declared with #[Attribute] attribute.

#[Attribute]
class FooAttribute{

}

By default, a declared attribute can be used on any item that accepts attributes. This includes classes, class methods, closures, functions, parameters, and class properties.

When declaring the attribute, it is possible to declare the targets the attribute must be used.

#[Attribute(Attribute::TARGET_CLASS)]
class Foo {}

When the attribute is attributed with the targets it supports, PHP does not allow the attribute to be used on any other targets. It accepts a bit-mask to allow the attribute in one or more targets.

#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
class Foo {}

It allows the following targets:

  • Attribute::TARGET_ALL
  • Attribute::TARGET_FUNCTION
  • Attribute::TARGET_METHOD
  • Attribute::TARGET_PROPERTY
  • Attribute::TARGET_CLASS_CONSTANT
  • Attribute::TARGET_PARAMETER
  • Attribute::TARGET_METHOD

TARGET_ALL is the OR of all other targets.

Attribute class is declared final

The Attribute class is declared final, which prevents it from being extended.

Repeatable Attributes

By default, it is not allowed to use the same attribute on the same target more than once. The attribute must explicitly allow it:

#[Attribute(Attribute::IS_REPEATABLE)]
class MyRepeatableAttribute{}

Attribute Syntax Example

#[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)] $a): string{}
}

// Declare Attributes

/*
 * Attributes are declared with `#[Attribute]`.
 */

#[Attribute]
class FooAttribute {
    public function __construct(?string $param1 = null) {}
}

#[Attribute]
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, auto-loaded, 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

#[ExampleAttribute('Hello world', 42)]
class Foo {}

#[Attribute]
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) {

    $attriubute->getName(); // "My\Attributes\ExampleAttribute"
    $attriubute->getArguments(); // ["Hello world", 42]
    $attriubute->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

The final syntax of Attributes feature is not backwards-compatible. However, because the # character is a valid symbol to start a comment, an attribute declared on its own line can make the code parse and run on earlier PHP versions.

#[ExampleAttribute('Hello world', 42)]
class Foo {}

If an attribute is used within the same line, the parser of prior PHP versions will not work:

function foo(#[TestAttr] $bar){
}
Parse error: syntax error, unexpected '}', expecting variable (T_VARIABLE) in ... on line ...

RFC Discussion Implementation