Attributes #[] Replacing Docblock Annotations
debt(d3/e5/b3/t5)
Closest to 'default linter catches the common case' (d3). PHPStan and Psalm (listed in detection_hints.tools) will flag missing #[Attribute] markers on custom attribute classes and usage in incompatible PHP versions. The syntax error from using #[] on PHP < 8.0 is caught at runtime/compile time immediately, pulling the score down slightly from d5.
Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix says to migrate framework DocBlock annotations to native #[] attributes and add #[Attribute] markers to custom attribute classes. This is not a single-line patch — it requires touching every annotated class/method across potentially multiple files, updating or replacing annotation-reader libraries, and verifying Reflection-based reading. It stays at e5 rather than e7 because it's typically scoped to annotation usage patterns rather than truly cross-cutting architectural change.
Closest to 'localised tax' (b3). The applies_to covers web, cli, and queue-worker contexts broadly, but attribute usage is localised to classes/methods that carry metadata. Once migrated, the ongoing burden is low — future maintainers work with standard PHP syntax rather than fragile string-parsed DocBlocks. The choice doesn't reshape the entire codebase architecture.
Closest to 'notable trap (a documented gotcha most devs eventually learn)' (t5). The misconception field explicitly states that developers confuse DocBlock annotations (string-parsed via Reflection::getDocComment() + regex) with native PHP attributes (first-class structures). The common mistake of using attributes without reading them via Reflection — expecting them to have automatic runtime effect — reinforces this as a well-documented but non-obvious gotcha. Not t7 because once understood, the distinction is clear and consistent.
TL;DR
Explanation
PHP 8.0 attributes: #[Attribute] on the attribute class, #[Route('/home')] on the target. Read with Reflection: (new ReflectionClass($obj))->getAttributes(Route::class). Benefits over DocBlocks: syntactically valid PHP (checked at parse time), no regex parsing, IDE completion, static analysis support, can be classes with validation. Built-in attributes: #[Override] (8.3), #[AllowDynamicProperties] (8.2), #[Deprecated] (8.4), #[ReturnTypeWillChange], #[SensitiveParameter] (8.2). Frameworks (Symfony, Laravel) have migrated from DocBlock annotations to native attributes.
Common Misconception
Why It Matters
Common Mistakes
- Using #[] syntax in PHP < 8.0 — fatal syntax error.
- Not declaring the #[Attribute] marker on custom attribute classes.
- Using attributes without reading them via Reflection — they have no effect at runtime on their own.
Code Examples
/**
* @Route('/home', methods={'GET'})
* @IsGranted('ROLE_USER')
*/
class HomeController {} // Parsed with fragile regex
#[Route('/home', methods: ['GET'])]
#[IsGranted('ROLE_USER')]
class HomeController {}
// Reading attributes:
$attrs = (new \ReflectionClass(HomeController::class))
->getAttributes(Route::class);
foreach ($attrs as $attr) {
$route = $attr->newInstance();
}