Atributos (PHP 8)
Nesta aula, você aprenderá sobre atributos no PHP 8, uma forma moderna de adicionar metadados estruturados a classes, métodos, propriedades, etc. Você verá a sintaxe #[...], como ler atributos via reflexão, casos de uso práticos e a comparação com as antigas anotações em docblock.
Os atributos (attributes) foram introduzidos no PHP 8 e representam uma evolução significativa na forma de adicionar metadados ao código. Antes, a comunidade dependia de anotações em docblocks (comentários com @) que precisavam ser interpretadas por bibliotecas, o que era frágil e sem validação. Agora, o PHP oferece uma sintaxe nativa e estruturada para declarar esses metadados, tornando o código mais explícito, seguro e legível.
Nesta aula, vamos explorar a fundo os atributos: como escrevê-los, como lê-los via reflexão, quais são os melhores casos de uso e como eles se comparam às anotações em docblock. Você entenderá por que os atributos são considerados uma das grandes adições do PHP moderno e como aplicá-los em projetos reais.
Sintaxe #[ ]
A sintaxe de atributos no PHP usa o formato #[ ... ] e pode ser aplicada a classes, métodos, propriedades, parâmetros, constantes e até mesmo closures. Eles são declarados imediatamente antes do elemento ao qual se aplicam. Os atributos podem receber argumentos, que podem ser valores constantes, arrays ou expressões simples.
Um atributo é, na verdade, uma classe que implementa a interface Attribute. Você define uma classe marcada com #[Attribute] para criar um atributo personalizado. Por exemplo:
#[Attribute]
class MinhaAnotacao {
public function __construct(public string $mensagem) {}
}
Para usar, basta colocar #[MinhaAnotacao('Olá')] antes de uma classe, método ou propriedade. É possível usar múltiplos atributos no mesmo elemento, separando por vírgula ou em linhas diferentes:
#[MinhaAnotacao('Olá')]
#[OutraAnotacao]
class Exemplo {}
Os atributos também podem ser aplicados a parâmetros de funções ou métodos, o que é útil para frameworks de validação ou injeção de dependência. A sintaxe é consistente e não interfere na execução normal do código: os atributos são apenas metadados que ficam disponíveis para reflexão.
Lendo via reflexão
Para que os atributos tenham utilidade, é necessário lê-los em tempo de execução. O PHP fornece uma API de reflexão completa para isso. As classes ReflectionClass, ReflectionMethod, ReflectionProperty e outras possuem o método getAttributes() que retorna uma lista de objetos ReflectionAttribute.
Cada ReflectionAttribute permite obter o nome da classe do atributo, seus argumentos e instanciar o atributo com os valores passados. Veja um exemplo completo:
#[Attribute]
class Rota {
public function __construct(public string $caminho) {}
}
#[Rota('/api/usuarios')]
class UsuarioController {
#[Rota('/api/usuarios/listar')]
public function listar() {}
}
$reflectionClass = new ReflectionClass(UsuarioController::class);
$atributosClasse = $reflectionClass->getAttributes();
foreach ($atributosClasse as $atributo) {
echo $atributo->getName() . PHP_EOL; // Rota
$instancia = $atributo->newInstance();
echo $instancia->caminho; // /api/usuarios
}
O método newInstance() cria uma instância do atributo, passando os argumentos definidos. Isso permite que você acesse os dados de forma tipada e segura. Você pode filtrar por um atributo específico usando getAttributes(Rota::class).
Casos de uso
Os atributos são extremamente versáteis e podem substituir muitas anotações docblock. Os casos de uso mais comuns incluem:
- Rotas em frameworks MVC: como o atributo
#[Route('/caminho')]em controladores, eliminando a necessidade de arquivos de configuração externos. - Validação de dados: atributos como
#[Required],#[MaxLength(255)]em propriedades de DTOs, permitindo validação automática. - Serialização: indicar como certas propriedades devem ser transformadas em JSON ou XML, como
#[JsonIgnore]ou#[XmlElement]. - Injeção de dependência: marcar parâmetros de construtores com
#[Inject]para que um container resolva automaticamente. - Testes: anotar métodos de teste com
#[DataProvider]ou#[Depends]para frameworks como PHPUnit.
Esses exemplos mostram como os atributos podem ser usados para criar código mais expressivo e autodocumentado, além de permitir que bibliotecas processem metadados de forma eficiente. Por exemplo, um framework de roteamento pode varrer os controladores e registrar as rotas automaticamente:
function registrarRotas(string $classe) {
$reflection = new ReflectionClass($classe);
foreach ($reflection->getMethods() as $metodo) {
$atributos = $metodo->getAttributes(Rota::class);
foreach ($atributos as $atributo) {
$rota = $atributo->newInstance();
echo $rota->caminho . ' => ' . $metodo->getName() . PHP_EOL;
}
}
}
vs anotações em docblock
Antes do PHP 8, era comum usar docblocks para anotar classes e métodos, como no PHPDoc: @param, @return, ou até anotações personalizadas como @Route. Bibliotecas como Doctrine e Symfony usavam essas anotações para configurar mapeamento objeto-relacional e rotas. No entanto, essas anotações eram apenas strings em comentários, sem validação, sem autocompletar e exigiam parsing manual com regex ou bibliotecas de reflection de docblock.
Os atributos resolvem essas limitações: são classes reais, com autoload, type safety e suporte de IDE. Eles são mais rápidos de processar, porque o PHP já os expõe via reflexão, sem precisar interpretar texto. Além disso, não há risco de conflitos com outras anotações ou erros de digitação silenciosos.
Entretanto, os docblocks ainda são úteis para documentação de tipos (como @param e @return) que não são executáveis. A recomendação é usar atributos para metadados que afetam o comportamento do código e docblocks para informações de documentação. Na transição, muitas bibliotecas ainda aceitam ambas as formas, mas o futuro é claramente os atributos.
Boas práticas
Ao criar atributos, prefira nomes que indiquem claramente sua função, como #[Route] em vez de #[Anotacao]. Mantenha os atributos pequenos e focados. Use o alvo do atributo (classe, método, propriedade) de forma consistente. Lembre-se de que atributos podem ser repetíveis ou não; por padrão, são não repetíveis, mas você pode definir #[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] para permitir múltiplas ocorrências. Sempre que possível, forneça valores padrão para os parâmetros do construtor para facilitar o uso.
Referências
- Documentação oficial de atributos no PHP
- Classe ReflectionAttribute
- ReflectionClass::getAttributes
- Classe Attribute
- RFC de Atributos (PHP 8)
- Visão geral de atributos
Exercícios
- Exercício 1: Crie um atributo chamado
Autorque aceite nome e email. Aplique-o a uma classe simples e, via reflexão, imprima os valores do atributo. - Exercício 2: Crie um atributo
Rotacom um parâmetro caminho. Aplique-o a dois métodos de uma classe e, via reflexão, liste os caminhos das rotas. - Exercício 3: Escreva um atributo
ValidaEmailque possa ser aplicado a propriedades. Em seguida, use reflexão para verificar se uma propriedade tem esse atributo e valide um valor. - Exercício 4: Crie um atributo repetível
Tagque aceite uma string. Aplique múltiplas tags a uma classe e leia todas via reflexão. - Exercício 5: Explique a diferença entre atributos e anotações docblock, destacando vantagens e desvantagens de cada um.
#[Attribute]
class Autor {
public function __construct(public string $nome, public string $email) {}
}
#[Autor('João', 'joao@example.com')]
class Livro {}
$ref = new ReflectionClass(Livro::class);
$atributos = $ref->getAttributes(Autor::class);
foreach ($atributos as $atributo) {
$autor = $atributo->newInstance();
echo $autor->nome . ' - ' . $autor->email;
}
#[Attribute]
class Rota {
public function __construct(public string $caminho) {}
}
class UsuarioController {
#[Rota('/usuarios')]
public function index() {}
#[Rota('/usuarios/novo')]
public function novo() {}
}
$ref = new ReflectionClass(UsuarioController::class);
$rotas = [];
foreach ($ref->getMethods() as $metodo) {
$atributos = $metodo->getAttributes(Rota::class);
foreach ($atributos as $atributo) {
$rota = $atributo->newInstance();
$rotas[] = $rota->caminho;
}
}
print_r($rotas);
#[Attribute(Attribute::TARGET_PROPERTY)]
class ValidaEmail {}
class Usuario {
#[ValidaEmail]
public string $email;
}
$ref = new ReflectionProperty(Usuario::class, 'email');
if ($ref->getAttributes(ValidaEmail::class)) {
$email = 'invalido';
if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
echo 'Válido';
} else {
echo 'Inválido';
}
}
#[Attribute(Attribute::IS_REPEATABLE)]
class Tag {
public function __construct(public string $nome) {}
}
#[Tag('php')]
#[Tag('programacao')]
class Curso {}
$ref = new ReflectionClass(Curso::class);
$tags = array_map(fn($attr) => $attr->newInstance()->nome, $ref->getAttributes(Tag::class));
print_r($tags);
Atributos são sintaxe nativa do PHP, processadas em tempo de compilação, com tipagem e validação. Docblocks são comentários que precisam ser interpretados por bibliotecas, sem validação. Atributos são mais rápidos, seguros e integrados ao ecossistema. Docblocks ainda são úteis para documentação de tipos, mas para metadados funcionais, atributos são superiores.