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.*