Atributos (PHP 8)
Esta aula aborda atributos (PHP 8), sua sintaxe com #[], leitura via reflexão, casos de uso práticos e comparação com anotações em docblock. O conteúdo inclui exemplos de código e exercícios com respostas.
Atributos foram introduzidos no PHP 8 como uma forma nativa de adicionar metadados a classes, métodos, propriedades e outros elementos. Diferente de comentários em docblock, atributos são estruturados e podem ser lidos programaticamente via reflexão, permitindo comportamentos dinâmicos e validações. Nesta aula, você aprenderá a sintaxe, como acessá-los e aplicá-los em cenários reais.
Atributos substituem a necessidade de anotações em docblock para muitos casos, trazendo tipagem forte e integração com o sistema de tipos do PHP. Eles são especialmente úteis em frameworks, ORMs e bibliotecas de validação.
Sintaxe #[ ]
A sintaxe de atributos usa colchetes duplos: #[NomeDoAtributo]. Eles podem ser aplicados a classes, métodos, propriedades, constantes, parâmetros e closures. Atributos podem aceitar argumentos, que podem ser constantes, expressões simples ou arrays.
Exemplo básico:
#[\Attribute]
class MyAttribute {
public function __construct(public string $name) {}
}
#[MyAttribute('example')]
class MyClass {}
Você pode usar múltiplos atributos no mesmo elemento, separados por vírgula ou em blocos separados. Também é possível usar atributos repetíveis marcando a classe do atributo com #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)].
Lendo via reflexão
A leitura de atributos é feita através da API de reflexão do PHP, como ReflectionClass, ReflectionMethod, ReflectionProperty, etc. O método getAttributes() retorna um array de objetos ReflectionAttribute.
$reflection = new ReflectionClass(MyClass::class);
$attributes = $reflection->getAttributes();
foreach ($attributes as $attribute) {
echo $attribute->getName() . "\n";
var_dump($attribute->getArguments());
}
Para instanciar o atributo, use $attribute->newInstance(), que chamará o construtor com os argumentos fornecidos. Isso permite acesso direto aos valores.
Casos de uso
Atributos são amplamente usados em:
- Mapeamento ORM: Como no Doctrine, para definir entidades e colunas.
- Validação: Validar dados de entrada com regras como #[NotEmpty], #[Email].
- Roteamento: Em frameworks como Symfony, para definir rotas em controllers.
- Injeção de dependência: Marcar serviços para autowiring.
- Serialização: Controlar como propriedades são serializadas (ex: JsonSerializable).
Exemplo de validação personalizada:
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class NotBlank {
public function __construct(public string $message = 'Value cannot be blank') {}
}
class User {
#[NotBlank(message: 'Name is required')]
public string $name;
}
// Validação via reflexão
function validate(object $object): void {
$reflection = new ReflectionClass($object);
foreach ($reflection->getProperties() as $property) {
$attributes = $property->getAttributes(NotBlank::class);
if (!empty($attributes) && empty($property->getValue($object))) {
throw new \InvalidArgumentException($attributes[0]->newInstance()->message);
}
}
}
vs anotações em docblock
Anotações em docblock (@annotation) são strings de comentário que exigem análise manual (ex: regex) ou bibliotecas como Doctrine Annotations. Elas são frágeis, sem verificação de tipo e sem suporte nativo da linguagem.
Atributos, por outro lado, são parte da sintaxe do PHP, com verificação de tipo, autocomplete em IDEs e performance melhor por não dependerem de parsing de comentários. Além disso, atributos podem ser alvo de verificação estática (como PHPStan).
Comparação prática:
// Docblock (antes do PHP 8)
/**
* @Route("/api/users", methods={"GET"})
*/
class UserController {}
// Atributo (PHP 8+)
#[Route('/api/users', methods: ['GET'])]
class UserController {}
Atributos são a evolução natural e recomendada para metadados em PHP moderno.
Boas práticas
- Prefira atributos a docblock para novos projetos.
- Defina atributos como classes dedicadas, com construtor tipado.
- Use constantes e expressões simples nos argumentos para evitar complexidade.
- Documente seus atributos com docblock para outros desenvolvedores.
Referências
- PHP Manual: Attributes
- PHP Manual: ReflectionAttribute
- PHP Manual: ReflectionClass::getAttributes
- Stitcher.io: Attributes in PHP 8
- Doctrine ORM: Attributes Reference
Exercícios
- Crie um atributo chamado `MinLength` que aceite um parâmetro `int $length` e uma mensagem opcional. Aplique-o a uma propriedade e escreva uma função de validação que use reflexão para verificar se o valor da propriedade tem pelo menos o comprimento especificado.✓ Resposta:
#[\Attribute(\Attribute::TARGET_PROPERTY)] class MinLength { public function __construct( public int $length, public string $message = 'Value is too short' ) {} } class User { #[MinLength(3, message: 'Name must have at least 3 characters')] public string $name; } function validateMinLength(object $object): void { $reflection = new ReflectionClass($object); foreach ($reflection->getProperties() as $property) { $attrs = $property->getAttributes(MinLength::class); if (!empty($attrs)) { $minLength = $attrs[0]->newInstance(); $value = $property->getValue($object); if (strlen($value) < $minLength->length) { throw new \InvalidArgumentException($minLength->message); } } } } - Use reflexão para listar todos os atributos de uma classe e seus argumentos, imprimindo o nome do atributo e os argumentos em formato legível.✓ Resposta:
function listAttributes(string $class): void { $reflection = new ReflectionClass($class); $attributes = $reflection->getAttributes(); foreach ($attributes as $attr) { echo $attr->getName() . ": "; print_r($attr->getArguments()); } } - Implemente um atributo `#[Route]` que aceite `string $path` e `array $methods`. Aplique a métodos de uma classe e crie um roteador simples que mapeie caminhos para métodos.✓ Resposta:
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class Route { public function __construct( public string $path, public array $methods = ['GET'] ) {} } class UserController { #[Route('/users', methods: ['GET'])] public function list(): void { echo "list users\n"; } #[Route('/users', methods: ['POST'])] public function create(): void { echo "create user\n"; } } function router(string $class, string $uri, string $method): void { $reflection = new ReflectionClass($class); foreach ($reflection->getMethods() as $methodRef) { $attrs = $methodRef->getAttributes(Route::class); foreach ($attrs as $attr) { $route = $attr->newInstance(); if ($route->path === $uri && in_array($method, $route->methods)) { $instance = $reflection->newInstanceWithoutConstructor(); $methodRef->invoke($instance); return; } } } throw new \RuntimeException("No route found"); } - Converta um docblock annotation `@Deprecated` em um atributo nativo. Crie o atributo e mostre como verificar se um método está marcado como deprecated.✓ Resposta:
#[\Attribute(\Attribute::TARGET_METHOD)] class Deprecated { public function __construct( public string $message = 'This method is deprecated' ) {} } class OldClass { #[Deprecated('Use newMethod instead')] public function oldMethod(): void {} } function checkDeprecated(string $class, string $method): void { $reflection = new ReflectionMethod($class, $method); $attrs = $reflection->getAttributes(Deprecated::class); if (!empty($attrs)) { $deprecated = $attrs[0]->newInstance(); trigger_error($deprecated->message, E_USER_DEPRECATED); } } - Crie um atributo `#[JsonExclude]` para propriedades que não devem ser serializadas em JSON. Implemente uma função de serialização que ignore propriedades com esse atributo.✓ Resposta:
#[\Attribute(\Attribute::TARGET_PROPERTY)] class JsonExclude {} class User { public string $name; #[JsonExclude] public string $password; } function toJson(object $object): string { $reflection = new ReflectionClass($object); $data = []; foreach ($reflection->getProperties() as $property) { if (!empty($property->getAttributes(JsonExclude::class))) { continue; } $property->setAccessible(true); $data[$property->getName()] = $property->getValue($object); } return json_encode($data); }