← Attributes

Ashterix

docs: English JsonRpcBundle docs, REST and MCP Bundle sections, package cards

en/articles/from-tg/series/php-can-do/attributes.md new file +163 βˆ’0

1 + # Attributes
2 +
3 + *Metaprogramming without DocBlock hacks*
4 +
5 + - **Added in:** PHP 8.0
6 + - **What it gives you:** a native, type-safe mechanism for adding metadata to classes, methods, properties and parameters β€” without having to parse DocBlocks or install third-party libraries to read them.
7 +
8 + ## A bit personal
9 +
10 + Before PHP 8.0 I had one technical "pain" that only people who have ever tried to build API documentation or an SDK by reading code know about.
11 +
12 + I'll say just one thing: parsing DocBlocks is a very thankless job.
13 +
14 + Attributes became the feature that literally gave my libraries "a breath of fresh air".
15 +
16 + The code became cleaner and simpler, and the metadata finally predictable.
17 +
18 + ## Why do we need them at all?
19 +
20 + Before PHP 8 metadata had to be written like this:
21 +
22 + ```php
23 + /**
24 + * @Route("/users")
25 + * @SomeFlag(true)
26 + */
27 + class UserController {}
28 + ```
29 +
30 + It was just a comment that standard PHP tools such as reflection don't parse. To analyse it you needed yet another parser library that had to guess what the developer meant.
31 +
32 + Attributes make meta-comments typed β€” now they're objects:
33 +
34 + ```php
35 + #[Route('/users')]
36 + #[SomeFlag(true)]
37 + class UserController {}
38 + ```
39 +
40 + This is literally a declaration of instances of the Route and SomeFlag classes, and the IDE knows exactly what's inside. This gives you:
41 +
42 + - native usage;
43 + - an understanding of which parameters need to be passed;
44 + - easy-to-read code;
45 + - easy use of this data.
46 +
47 + ## Examples of use
48 +
49 + ### 1. Doctrine
50 +
51 + ```php
52 + #[ORM\Entity]
53 + class Product
54 + {
55 + #[ORM\Id]
56 + #[ORM\GeneratedValue]
57 + #[ORM\Column]
58 + public int $id;
59 +
60 + #[ORM\Column(length: 255)]
61 + public string $name;
62 + }
63 + ```
64 +
65 + ### 2. Symfony Routing
66 +
67 + ```php
68 + #[Route('/orders/{id}', methods: ['GET'])]
69 + public function show(int $id) { ... }
70 + ```
71 +
72 + ### 3. Validation
73 +
74 + ```php
75 + class RegistrationDTO
76 + {
77 + #[Assert\Email]
78 + #[Assert\NotBlank]
79 + public string $email;
80 +
81 + #[Assert\Length(min: 8)]
82 + public string $password;
83 + }
84 + ```
85 +
86 + ### 4. Custom attributes
87 +
88 + ```php
89 + #[API\Method('user.create')]
90 + class CreateUserRequest
91 + {
92 + #[API\Param]
93 + public string $email;
94 +
95 + #[API\Param]
96 + public string $password;
97 + }
98 + ```
99 +
100 + ## What can you do with attributes?
101 +
102 + Almost everything that Java/Spring or C# attributes do:
103 +
104 + - mark classes and methods;
105 + - add metadata;
106 + - create your own declarative rules;
107 + - build routing;
108 + - mark up serialization/deserialization;
109 + - automate DI/autowiring;
110 + - specify how caching or logging works;
111 + - create declarative contracts for DTOs.
112 +
113 + In fact, it's a tool for writing less imperative code and more declarative code.
114 +
115 + ## How to create your own attribute
116 +
117 + ```php
118 + #[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
119 + class Loggable
120 + {
121 + public function __construct(
122 + public string $message = 'default',
123 + public bool $enabled = true,
124 + ) {}
125 + }
126 + ```
127 +
128 + Usage:
129 +
130 + ```php
131 + #[Loggable(message: 'Auth attempt')]
132 + class AuthService {}
133 + ```
134 +
135 + ## How to read an attribute via Reflection
136 +
137 + ```php
138 + $ref = new ReflectionClass(AuthService::class);
139 + $attrs = $ref->getAttributes(Loggable::class);
140 +
141 + $instance = $attrs[0]->newInstance();
142 + echo $instance->message;
143 + ```
144 +
145 + ## Especially important
146 +
147 + - an attribute is a full-fledged object, not just a string in a comment;
148 + - attributes have a namespace;
149 + - parameters can be typed;
150 + - you can use advanced constructs (enum, readonly, VO);
151 + - they combine perfectly with DI containers.
152 +
153 + ## Conclusion
154 +
155 + > Attributes are one of those features after which old PHP seems archaic.
156 + > Type safety, a declarative style, transparent handling via Reflection and no DocBlock magic β€” all of this makes them a tool I love using in my own libraries.
157 + > It feels so natural that it's hard to believe we once wrote metadata in comments.
158 +
159 + ← [Previous: readonly + accessors](/articles/from-tg/series/php-can-do/readonly) Β· [Whole series](/articles/from-tg/series/php-can-do)
160 +
161 + ---
162 +
163 + *First published in Ukrainian in the Telegram channel [β€œCreated to die”](https://t.me/php_while_true/96) on December 9, 2025.*