Attributes (Annotations in Code)

Attributes attach structured, machine-readable metadata to classes, methods and properties — read back at runtime via Reflection.

Before PHP 8, metadata lived in docblock comments that tools parsed as strings. Attributes make that metadata a first-class part of the language: real classes, written in #[...] syntax, that you attach to declarations and read back with Reflection. Routing, validation, ORM mapping and DI all use them.

Easy example

<?php
#[Attribute]
class Route {
    public function __construct(public string $path) {}
}

class HomeController {
    #[Route('/home')]
    public function index() {}
}
// the #[Route('/home')] is inert until Reflection reads it

Attributes do nothing on their own — they're passive data. Some code has to reflect over them and act, which keeps them cheap and side-effect-free until you ask.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Mark route handler methods with a #[Route] attribute that a router reads via Reflection at bootstrap time.
  • Annotate ORM entity properties with #[Column] and #[Id] attributes that a schema generator processes.
  • Use #[Deprecated] on methods to communicate API contract changes that tools and IDEs can surface.

More examples

Defining and applying an attribute

A class decorated with #[Attribute] becomes reusable metadata; Attribute::TARGET_METHOD restricts it to methods only.

Example · php
<?php
#[Attribute(Attribute::TARGET_METHOD)]
class Route {
    public function __construct(
        public readonly string $path,
        public readonly string $method = 'GET'
    ) {}
}

class UserController {
    #[Route('/users', 'GET')]
    public function index(): void { echo 'User list'; }

    #[Route('/users', 'POST')]
    public function store(): void { echo 'Create user'; }
}

Reading attributes via Reflection

ReflectionClass reads attributes at runtime; newInstance() instantiates the attribute class with its constructor arguments.

Example · php
<?php
#[Attribute(Attribute::TARGET_METHOD)]
class Route {
    public function __construct(public string $path, public string $method = 'GET') {}
}

class PostController {
    #[Route('/posts')]
    public function index(): void {}
}

$ref = new ReflectionClass(PostController::class);
foreach ($ref->getMethods() as $method) {
    foreach ($method->getAttributes(Route::class) as $attr) {
        $route = $attr->newInstance();
        echo "{$route->method} {$route->path} -> {$method->name}\n";
    }
}

Built-in #[Deprecated] attribute

#[Deprecated] is a PHP 8.4 built-in attribute; IDEs and static analysers surface its message when the method is called.

Example · php
<?php
class ApiClient {
    #[\Deprecated(
        message: 'Use fetchJson() instead',
        since: '2.0'
    )]
    public function fetch(string $url): string {
        return file_get_contents($url);
    }

    public function fetchJson(string $url): array {
        return json_decode(file_get_contents($url), true);
    }
}

Discussion

  • Be the first to comment on this lesson.