With the release of PHP 8.0, developers gained a long-awaited feature: Attributes (also known as annotations in other languages like Java or C#).

Attributes allow you to add metadata to classes, methods, properties, and other structures in a clean, structured way. Instead of relying on PHPDoc comments or external configuration, you can now attach additional information directly in the code and retrieve it at runtime.


What are Attributes?

Attributes are structured metadata that you can attach to PHP code elements such as:

  • Classes
  • Methods
  • Functions
  • Properties
  • Parameters
  • Class constants

This metadata can then be inspected at runtime using the Reflection API.

- Why do we need Attributes?

Before attributes, developers often used:

  • PHPDoc annotations (parsed with third-party libraries)
  • Configuration files (YAML, XML, JSON)

Example (Doctrine ORM using PHPDoc):

/**
 * @Entity
 * @Table(name="users")
 */
class User {
    /** @Column(type="string") */
    private $name;
}

With attributes, we can now write:

#[Entity]
#[Table(name: "users")]
class User {
    #[Column(type: "string")]
    private string $name;
}

Cleaner, native, and type-safe.


Attribute Syntax

Attributes in PHP are written inside #[ ... ].

Example: Multiple Attributes

#[Route("/home")]
#[Middleware("auth")]
function homePage() {
    return "Welcome!";
}

Here:

  • Route is one attribute specifying the URL path.
  • Middleware is another attribute specifying that the route requires authentication.

Attributes with Arguments

Attributes can take arguments, just like function calls.

#[Cache(ttl: 3600)]
function getProducts() {
    // ...
}
  • Cache is the attribute.
  • ttl: 3600 passes a named argument.

Grouped Attributes

You can group multiple attributes in a single #[ ... ].

#[Route("/dashboard"), Middleware("auth")]
function dashboard() {
    // ...
}

This is equivalent to writing them separately.


Reading Attributes with the Reflection API

To use attributes effectively, you need to read them at runtime. For that, PHP provides new methods in the Reflection API.

- Example: Getting Method Attributes

#[Route("/home")]
#[Middleware("auth")]
function homePage() {
    return "Welcome!";
}

$reflection = new ReflectionFunction('homePage');
$attributes = $reflection->getAttributes();

foreach ($attributes as $attribute) {
    echo "Attribute: " . $attribute->getName();
    print_r($attribute->getArguments());
}

Output:

Attribute: Route
Array
(
    [0] => /home
)
Attribute: Middleware
Array
(
    [0] => auth
)

- Example: Getting Class Attributes

#[Entity]
#[Table(name: "users")]
class User {}

$reflection = new ReflectionClass(User::class);

foreach ($reflection->getAttributes() as $attribute) {
    echo $attribute->getName();
}

// Entity
// Table

Attribute Targets

You can specify where your attribute can be applied (class, method, property, etc.) using Attribute::TARGET_* constants.

#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
class Route
{
    public function __construct(public string $path) {}
}

This means Route can be used only on classes and methods.


Source: Orkhan Alishov's notes