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
Attributes
Metaprogramming without DocBlock hacks
- Added in: PHP 8.0
- 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.
A bit personal
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.
I'll say just one thing: parsing DocBlocks is a very thankless job.
Attributes became the feature that literally gave my libraries "a breath of fresh air".
The code became cleaner and simpler, and the metadata finally predictable.
Why do we need them at all?
Before PHP 8 metadata had to be written like this:
/**
* @Route("/users")
* @SomeFlag(true)
*/
class UserController {}
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.
Attributes make meta-comments typed — now they're objects:
#[Route('/users')]
#[SomeFlag(true)]
class UserController {}
This is literally a declaration of instances of the Route and SomeFlag classes, and the IDE knows exactly what's inside. This gives you:
- native usage;
- an understanding of which parameters need to be passed;
- easy-to-read code;
- easy use of this data.
Examples of use
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. Validation
class RegistrationDTO
{
#[Assert\Email]
#[Assert\NotBlank]
public string $email;
#[Assert\Length(min: 8)]
public string $password;
}
4. Custom attributes
#[API\Method('user.create')]
class CreateUserRequest
{
#[API\Param]
public string $email;
#[API\Param]
public string $password;
}
What can you do with attributes?
Almost everything that Java/Spring or C# attributes do:
- mark classes and methods;
- add metadata;
- create your own declarative rules;
- build routing;
- mark up serialization/deserialization;
- automate DI/autowiring;
- specify how caching or logging works;
- create declarative contracts for DTOs.
In fact, it's a tool for writing less imperative code and more declarative code.
How to create your own attribute
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
class Loggable
{
public function __construct(
public string $message = 'default',
public bool $enabled = true,
) {}
}
Usage:
#[Loggable(message: 'Auth attempt')]
class AuthService {}
How to read an attribute via Reflection
$ref = new ReflectionClass(AuthService::class);
$attrs = $ref->getAttributes(Loggable::class);
$instance = $attrs[0]->newInstance();
echo $instance->message;
Especially important
- an attribute is a full-fledged object, not just a string in a comment;
- attributes have a namespace;
- parameters can be typed;
- you can use advanced constructs (enum, readonly, VO);
- they combine perfectly with DI containers.
Conclusion
Attributes are one of those features after which old PHP seems archaic. 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. It feels so natural that it's hard to believe we once wrote metadata in comments.
First published in Ukrainian in the Telegram channel “Created to die” on December 9, 2025.