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 itAttributes 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
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.
<?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.
<?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.
<?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