Introduction
Attributes, introduced in PHP 8.0, are structured metadata annotations that can be attached to classes, methods, properties, parameters, and constants. They replace the informal docblock annotation pattern with a native, type-safe, reflectable solution.
Key Concepts
- Attribute: A PHP class decorated with
#[Attribute]that can be attached to code elements using#[...]syntax. - Target: The kind of code element an attribute can be applied to (class, method, property, parameter, constant).
- Repeatable Attribute: An attribute that can appear multiple times on the same target.
Real World Context
Before attributes, PHP frameworks used docblock annotations (@Route, @ORM\Column) parsed by regex. These were fragile, not validated by PHP, and invisible to the engine. Native attributes are type-checked, IDE-friendly, and reflectable without third-party parsers. Symfony, Laravel, and Doctrine all migrated to native attributes.
Deep Dive
Basic Syntax
Define an attribute class and use it:
php<?php #[Attribute] class Route { public function __construct( public string $path, public string $method = 'GET' ) {} } #[Route('/users', 'GET')] class UserController { #[Route('/users/{id}', 'GET')] public function show(int $id): User {} #[Route('/users', 'POST')] public function create(): User {} }
The #[Attribute] annotation on the class tells PHP it can be used as an attribute. The #[Route(...)] syntax applies it.
Built-in Attributes
PHP provides several built-in attributes:
#[Override] (PHP 8.3+)
Ensures a method actually overrides a parent method:
php<?php class Animal { public function speak(): string { return 'Some sound'; } } class Dog extends Animal { #[Override] public function speak(): string { return 'Woof!'; } // OK #[Override] public function bark(): string { return 'Bark!'; } // Error! No parent method }
This catches refactoring bugs where the parent method is renamed but child classes are not updated.
#[Deprecated] (PHP 8.4+)
Mark code as deprecated with a message:
php<?php class Api { #[Deprecated('Use newMethod() instead', since: '2.0')] public function oldMethod(): void {} public function newMethod(): void {} }
PHP 8.5 extends #[Deprecated] to traits and constants, not just methods and classes.
#[SensitiveParameter] (PHP 8.2+)
Hide sensitive values in stack traces:
php<?php function authenticate( string $username, #[SensitiveParameter] string $password ): bool { return verify($username, $password); }
If an exception occurs, $password is replaced with SensitiveParameterValue in the stack trace.
#[NoDiscard] (PHP 8.5)
Warn when a function's return value is ignored:
php<?php #[NoDiscard] function computeChecksum(string $data): string { return hash('sha256', $data); } computeChecksum($data); // Warning: return value not used $checksum = computeChecksum($data); // OK (void) computeChecksum($data); // OK — explicitly discarded with (void) cast
This is useful for pure functions where ignoring the return value is almost certainly a bug.
Attribute Targets
Restrict where an attribute can be used:
php<?php #[Attribute(Attribute::TARGET_CLASS)] // Only on classes #[Attribute(Attribute::TARGET_METHOD)] // Only on methods #[Attribute(Attribute::TARGET_PROPERTY)] // Only on properties #[Attribute(Attribute::TARGET_PARAMETER)] // Only on parameters #[Attribute(Attribute::TARGET_CLASS_CONSTANT)] // Only on constants #[Attribute(Attribute::TARGET_ALL)] // Anywhere (default) // Combine targets with bitwise OR #[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)] class MyAttribute {}
Target restrictions are enforced at the time the attribute is instantiated via reflection.
Repeatable Attributes
Allow multiple instances on the same target:
php<?php #[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] class Middleware { public function __construct(public string $name) {} } class Controller { #[Middleware('auth')] #[Middleware('logging')] #[Middleware('cache')] public function index(): Response {} }
Without IS_REPEATABLE, applying the same attribute twice would cause an error.
Common Pitfalls
- Forgetting
#[Attribute]on the class — Without the#[Attribute]annotation, PHP will not recognize the class as a valid attribute and will throw an error when you try to use it. - Using attributes without reading them — Attributes are metadata only. They have no effect unless code reads them via reflection. Simply adding
#[Route('/')]does nothing without a router that scans for it.
Best Practices
- Set specific targets — Use
Attribute::TARGET_METHODinstead of the defaultTARGET_ALLto catch misuse at runtime. - Use constructor promotion in attributes — Attributes are classes, so
public function __construct(public string $path)keeps them concise.
Summary
- Attributes are native metadata annotations using
#[...]syntax. - Define attribute classes with
#[Attribute]and constructor parameters. - Built-in attributes include
#[Override],#[Deprecated],#[SensitiveParameter], and#[NoDiscard](PHP 8.5). - Control where attributes can appear with target flags.
- Use
IS_REPEATABLEto allow multiple instances on one target.
Code Examples
<?php
declare(strict_types=1);
#[Attribute(Attribute::TARGET_PROPERTY)]
class Required {
public function __construct(public string $message = 'Field is required') {}
}
#[Attribute(Attribute::TARGET_PROPERTY)]
class Email {
public function __construct(public string $message = 'Invalid email format') {}
}
#[Attribute(Attribute::TARGET_PROPERTY)]
class MinLength {
public function __construct(
public int $min,
public string $message = 'Too short'
) {}
}
class UserDTO {
#[Required]
#[MinLength(2)]
public string $name;
#[Required]
#[Email]
public string $email;
#[Required]
#[MinLength(8, message: 'Password must be at least 8 characters')]
public string $password;
}
?>