← Атрибути

Ashterix

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

uk/articles/from-tg/series/php-can-do/attributes.md новий файл +163 −0

1 + # Атрибути
2 +
3 + *Метапрограмування без DocBlock-хаків*
4 +
5 + - **Додано у версії:** PHP 8.0
6 + - **Що дає:** нативний, типобезпечний механізм додавання метаданих до класів, методів, властивостей та параметрів — без необхідності при їх читанні парсити DocBlock-и та встановлювати сторонні бібліотеки.
7 +
8 + ## Трохи особистого
9 +
10 + До PHP 8.0 в мене був один технічний “біль”, про який знають тільки ті, хто коли-небудь пробував будувати API-документацію чи SDK на основі читання коду.
11 +
12 + Скажу лише одне: парсинг DocBlock-ів — це дуже невдячна робота.
13 +
14 + Атрибути стали тією фічею, після якої мої бібліотеки буквально отримали "вдих свіжого повітря".
15 +
16 + Код став чистішим, простішим, а метадані — нарешті передбачуваними.
17 +
18 + ## Навіщо вони взагалі потрібні?
19 +
20 + До PHP 8 метадані доводилося писати так:
21 +
22 + ```php
23 + /**
24 + * @Route("/users")
25 + * @SomeFlag(true)
26 + */
27 + class UserController {}
28 + ```
29 +
30 + Це був просто коментар, який не розбирається стандартними засобами PHP такими як рефлексія. Для аналізу потрібна ще одна бібліотека-парсер, яка мала вгадати, що розробник мав на увазі.
31 +
32 + Атрибути роблять метакоментарі типізованими, тепер вони обʼєктні:
33 +
34 + ```php
35 + #[Route('/users')]
36 + #[SomeFlag(true)]
37 + class UserController {}
38 + ```
39 +
40 + Це буквально декларація екземплярів класів Route та SomeFlag, і IDE точно знає, що всередині. Це дає:
41 +
42 + - нативне використання;
43 + - розуміння які параметри треба передати;
44 + - легке читання коду;
45 + - легке використання цих даних.
46 +
47 + ## Приклади застосування
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. Валідація
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. Кастомні атрибути
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 + ## А що можна робити з атрибутами?
101 +
102 + Майже все, що роблять Java/Spring- або C#-атрибути:
103 +
104 + - позначати класи й методи;
105 + - додавати метадані;
106 + - створювати власні декларативні правила;
107 + - будувати роутинг;
108 + - маркувати серіалізацію/десеріалізацію;
109 + - автоматизувати DI/автовайринг;
110 + - вказувати, як працює кешування чи логування;
111 + - створювати декларативні контракти для DTO.
112 +
113 + Фактично — інструмент, щоб писати менше імперативного коду та більше декларативного.
114 +
115 + ## Як створити власний атрибут
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 + Використання:
129 +
130 + ```php
131 + #[Loggable(message: 'Auth attempt')]
132 + class AuthService {}
133 + ```
134 +
135 + ## Як прочитати атрибут через 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 + ## Особливо важливо
146 +
147 + - атрибути — це повноцінний об’єкт, а не просто рядок в коментарі;
148 + - вони мають простір імен;
149 + - можна типізувати параметри;
150 + - можна використовувати передові конструкції (enum, readonly, VO);
151 + - ідеально комбінуються з DI-контейнерами.
152 +
153 + ## Висновок
154 +
155 + > Атрибути — це одна з тих фіч, після яких старий PHP здається архаїчним.
156 + > Типобезпека, декларативність, прозора робота через Reflection, відсутність DocBlock-магії — це все робить їх інструментом, який я обожнюю використовувати у власних бібліотеках.
157 + > Це настільки природно, що важко повірити, що колись ми писали метадані в коментарях.
158 +
159 + ← [Попередня: readonly + accessors](/articles/from-tg/series/php-can-do/readonly) · [Уся серія](/articles/from-tg/series/php-can-do)
160 +
161 + ---
162 +
163 + *Вперше опубліковано в Telegram-каналі [«Створений щоб помирати»](https://t.me/php_while_true/96) 09.12.2025.*