Атрибути
Метапрограмування без DocBlock-хаків
- Додано у версії: PHP 8.0
- Що дає: нативний, типобезпечний механізм додавання метаданих до класів, методів, властивостей та параметрів — без необхідності при їх читанні парсити DocBlock-и та встановлювати сторонні бібліотеки.
Трохи особистого
До PHP 8.0 в мене був один технічний “біль”, про який знають тільки ті, хто коли-небудь пробував будувати API-документацію чи SDK на основі читання коду.
Скажу лише одне: парсинг DocBlock-ів — це дуже невдячна робота.
Атрибути стали тією фічею, після якої мої бібліотеки буквально отримали "вдих свіжого повітря".
Код став чистішим, простішим, а метадані — нарешті передбачуваними.
Навіщо вони взагалі потрібні?
До PHP 8 метадані доводилося писати так:
/**
* @Route("/users")
* @SomeFlag(true)
*/
class UserController {}
Це був просто коментар, який не розбирається стандартними засобами PHP такими як рефлексія. Для аналізу потрібна ще одна бібліотека-парсер, яка мала вгадати, що розробник мав на увазі.
Атрибути роблять метакоментарі типізованими, тепер вони обʼєктні:
#[Route('/users')]
#[SomeFlag(true)]
class UserController {}
Це буквально декларація екземплярів класів Route та SomeFlag, і IDE точно знає, що всередині. Це дає:
- нативне використання;
- розуміння які параметри треба передати;
- легке читання коду;
- легке використання цих даних.
Приклади застосування
1. Doctrine
#[ORM\Entity]
class Product
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
public int $id;
#[ORM\Column(length: 255)]
public string $name;
}
2. Symfony Routing
#[Route('/orders/{id}', methods: ['GET'])]
public function show(int $id) { ... }
3. Валідація
class RegistrationDTO
{
#[Assert\Email]
#[Assert\NotBlank]
public string $email;
#[Assert\Length(min: 8)]
public string $password;
}
4. Кастомні атрибути
#[API\Method('user.create')]
class CreateUserRequest
{
#[API\Param]
public string $email;
#[API\Param]
public string $password;
}
А що можна робити з атрибутами?
Майже все, що роблять Java/Spring- або C#-атрибути:
- позначати класи й методи;
- додавати метадані;
- створювати власні декларативні правила;
- будувати роутинг;
- маркувати серіалізацію/десеріалізацію;
- автоматизувати DI/автовайринг;
- вказувати, як працює кешування чи логування;
- створювати декларативні контракти для DTO.
Фактично — інструмент, щоб писати менше імперативного коду та більше декларативного.
Як створити власний атрибут
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
class Loggable
{
public function __construct(
public string $message = 'default',
public bool $enabled = true,
) {}
}
Використання:
#[Loggable(message: 'Auth attempt')]
class AuthService {}
Як прочитати атрибут через Reflection
$ref = new ReflectionClass(AuthService::class);
$attrs = $ref->getAttributes(Loggable::class);
$instance = $attrs[0]->newInstance();
echo $instance->message;
Особливо важливо
- атрибути — це повноцінний об’єкт, а не просто рядок в коментарі;
- вони мають простір імен;
- можна типізувати параметри;
- можна використовувати передові конструкції (enum, readonly, VO);
- ідеально комбінуються з DI-контейнерами.
Висновок
Атрибути — це одна з тих фіч, після яких старий PHP здається архаїчним. Типобезпека, декларативність, прозора робота через Reflection, відсутність DocBlock-магії — це все робить їх інструментом, який я обожнюю використовувати у власних бібліотеках. Це настільки природно, що важко повірити, що колись ми писали метадані в коментарях.
← Попередня: readonly + accessors · Уся серія
Вперше опубліковано в Telegram-каналі «Створений щоб помирати» 09.12.2025.