Ответ
Аннотации в PHP (официально называемые атрибутами, attributes) — это форма метаданных, которая добавляется к классам, методам, свойствам или параметрам для предоставления дополнительной информации. Они были введены в PHP 8.0 и представляют собой структурированную альтернативу док-блокам с аннотациями.
Синтаксис и базовое использование:
// Объявление атрибута (класс атрибута)
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
class Route {
public function __construct(
public string $path,
public array $methods = ['GET']
) {}
}
// Применение атрибута к классу и методу
#[Route('/api/users')]
class UserController {
#[Route('/{id}', methods: ['GET'])]
public function show(int $id): Response {
// ...
}
}
Как я использую атрибуты в реальных проектах:
-
Маршрутизация в фреймворках:
// Symfony с атрибутами #[Route('/api/products')] class ProductController { #[Route('/{id}', name: 'product_show', methods: ['GET'])] #[IsGranted('ROLE_USER')] public function show(Product $product): JsonResponse { return $this->json($product); } #[Route('', name: 'product_create', methods: ['POST'])] #[IsGranted('ROLE_ADMIN')] #[ValidateRequest] public function create(Request $request): JsonResponse { // ... } } -
Валидация данных:
class UserDto { #[AssertNotBlank] #[AssertLength(min: 3, max: 50)] public string $username; #[AssertNotBlank] #[AssertEmail] public string $email; #[AssertChoice(['male', 'female', 'other'])] public string $gender; #[AssertRange(min: 18, max: 100)] public int $age; } -
Маппинг Doctrine ORM:
#[Entity] #[Table(name: 'users')] class User { #[Id] #[GeneratedValue] #[Column(type: 'integer')] private ?int $id = null; #[Column(type: 'string', length: 180, unique: true)] private string $email; #[OneToMany(mappedBy: 'user', targetEntity: Order::class)] private Collection $orders; #[Column(type: 'datetime')] private DateTimeInterface $createdAt; public function __construct() { $this->createdAt = new DateTimeImmutable(); $this->orders = new ArrayCollection(); } } -
Кастомные атрибуты для логики приложения:
// Атрибут для кэширования результатов метода #[Attribute(Attribute::TARGET_METHOD)] class CacheResult { public function __construct( public int $ttl = 3600, public ?string $key = null ) {} } // Обработчик атрибута class CacheResultHandler { public function handle(object $object, string $method, array $args): mixed { $reflection = new ReflectionMethod($object, $method); $attributes = $reflection->getAttributes(CacheResult::class); if (empty($attributes)) { return $object->$method(...$args); } $cacheAttr = $attributes[0]->newInstance(); $cacheKey = $cacheAttr->key ?? $this->generateKey($object, $method, $args); if ($cached = Cache::get($cacheKey)) { return $cached; } $result = $object->$method(...$args); Cache::set($cacheKey, $result, $cacheAttr->ttl); return $result; } }
Преимущества перед док-блоками:
- Выполняемый код: Атрибуты — это реальные классы PHP, которые можно инстанциировать и использовать
- Проверка типов: IDE и статические анализаторы понимают типы параметров
- Автодополнение: PhpStorm подсказывает доступные параметры атрибутов
- Производительность: Не нужно парсить строки, как в случае с аннотациями в док-блоках
- Структурированные данные: Параметры атрибутов типизированы и валидируются
Чтение атрибутов через Reflection API:
$reflectionClass = new ReflectionClass(UserController::class);
$attributes = $reflectionClass->getAttributes(Route::class);
if (!empty($attributes)) {
$routeAttribute = $attributes[0]->newInstance();
echo "Path: " . $routeAttribute->path; // /api/users
}
Атрибуты значительно улучшили опыт работы с метаданными в PHP, сделав код более типобезопасным и удобным для анализа инструментами разработки.
Ответ 18+ 🔞
Ну, вот смотри, объясню тебе про эти ваши атрибуты в PHP, или, как их ещё, анноции ёпта. Это ж, блядь, просто метаданные, которые ты к классам или методам цепляешь, чтобы пояснить, что тут к чему. Раньше в док-блоках писали, а теперь с восьмой версии — отдельная синтаксическая хуйня, прям в коде. Овердохуища удобства, честно.
Как это выглядит, блядь:
// Сначала объявляешь сам атрибут, это обычный класс, ёпта
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
class Route {
public function __construct(
public string $path,
public array $methods = ['GET']
) {}
}
// А потом вешаешь его куда надо
#[Route('/api/users')]
class UserController {
#[Route('/{id}', methods: ['GET'])]
public function show(int $id): Response {
// ...
}
}
А вот где я их на практике применяю, чтоб ты понимал масштаб:
-
Роутинг в фреймворках. Это вообще пиздец как удобно стало, не надо в отдельный конфиг лазить.
// Взял, например, Symfony #[Route('/api/products')] class ProductController { #[Route('/{id}', name: 'product_show', methods: ['GET'])] #[IsGranted('ROLE_USER')] // И права проверяй тут же, ебать колотить public function show(Product $product): JsonResponse { return $this->json($product); } #[Route('', name: 'product_create', methods: ['POST'])] #[IsGranted('ROLE_ADMIN')] #[ValidateRequest] // И валидацию прикрутил, хитрая жопа public function create(Request $request): JsonResponse { // ... } } -
Валидация данных. Раньше в триста мест писать надо было, а теперь прям над полями класса.
class UserDto { #[AssertNotBlank] // Не пустое, блядь! #[AssertLength(min: 3, max: 50)] // И чтоб не короткое и не длинное public string $username; #[AssertNotBlank] #[AssertEmail] // Чтоб почта настоящая, а не хуй с горы public string $email; #[AssertChoice(['male', 'female', 'other'])] // Выбирай из списка, ёпта public string $gender; #[AssertRange(min: 18, max: 100)] // От 18 до 100, ядрёна вошь, логично же public int $age; } -
Doctrine ORM маппинг. Тут вообще красота, вся схема БД прямо в ентити.
#[Entity] #[Table(name: 'users')] class User { #[Id] #[GeneratedValue] #[Column(type: 'integer')] private ?int $id = null; #[Column(type: 'string', length: 180, unique: true)] private string $email; #[OneToMany(mappedBy: 'user', targetEntity: Order::class)] private Collection $orders; #[Column(type: 'datetime')] private DateTimeInterface $createdAt; public function __construct() { $this->createdAt = new DateTimeImmutable(); $this->orders = new ArrayCollection(); } } -
Свои кастомные атрибуты. Вот тут вообще полёт фантазии, делай что хочешь. Например, кеширование результатов метода.
// Объявляешь атрибут для кеша #[Attribute(Attribute::TARGET_METHOD)] class CacheResult { public function __construct( public int $ttl = 3600, public ?string $key = null ) {} } // А потом пишешь хендлер, который это всё обрабатывает class CacheResultHandler { public function handle(object $object, string $method, array $args): mixed { $reflection = new ReflectionMethod($object, $method); $attributes = $reflection->getAttributes(CacheResult::class); // Если атрибута нет — просто выполняем метод if (empty($attributes)) { return $object->$method(...$args); } // Достаём параметры атрибута $cacheAttr = $attributes[0]->newInstance(); $cacheKey = $cacheAttr->key ?? $this->generateKey($object, $method, $args); // Проверяем кеш if ($cached = Cache::get($cacheKey)) { return $cached; } // Если нет в кеше — выполняем и сохраняем $result = $object->$method(...$args); Cache::set($cacheKey, $result, $cacheAttr->ttl); return $result; } }
И чем это лучше старых док-блоков, спросишь ты? А вот чем, ёпта:
- Это реальный код, блядь. Не строки для парсера, а классы, которые можно создать и использовать.
- Проверка типов. IDE не будет орать, что ты строку туда, где массив нужен.
- Автодополнение в том же PhpStorm. Начал писать
#[Rout— он тебе ужеRouteподсказывает и его параметры. - Быстрее. Никакого парсинга строк — всё нативно.
- Структурированно. Всё типизировано, никакого «а вдруг разработчик опечатался в названии параметра».
Как их через рефлексию читать, если вдруг понадобится:
$reflectionClass = new ReflectionClass(UserController::class);
$attributes = $reflectionClass->getAttributes(Route::class);
if (!empty($attributes)) {
$routeAttribute = $attributes[0]->newInstance();
echo "Path: " . $routeAttribute->path; // Выведет /api/users, всё просто
}
Короче, атрибуты — это мощный инструмент, который делает код чище и умнее. Сначала кажется, что лишняя морока, но потом понимаешь — жить без них уже не можешь, как без этого всего.