Atributos nativos: metadados no código
1. Introdução aos Atributos no PHP 8.0
Atributos nativos foram introduzidos no PHP 8.0 como uma forma estruturada e tipada de adicionar metadados ao código. Antes deles, desenvolvedores dependiam de docblocks (PHPDoc) para anotar classes, métodos e propriedades — uma abordagem baseada em strings, sem validação sintática ou semântica pela engine do PHP.
A principal diferença entre atributos nativos e docblocks é que atributos são construções da linguagem, não comentários. Enquanto docblocks são ignorados pelo interpretador (a menos que uma biblioteca os leia via reflexão de texto), atributos são parseados e validados pelo PHP, permitindo que ferramentas como IDEs, analisadores estáticos e frameworks os consumam de forma confiável.
A sintaxe básica é simples: #[Atributo] para um atributo sem argumentos, ou #[Atributo(argumentos)] para configurá-lo com parâmetros.
#[Route('/api/users')]
class UserController {
#[NotEmpty]
public string $name;
}
2. Estrutura e Declaração de Atributos
Para criar um atributo customizado, você define uma classe comum e a anota com o atributo #[Attribute]. Opcionalmente, especifica os alvos permitidos e se o atributo pode ser repetido no mesmo elemento.
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_METHOD)]
class NotEmpty {
public function __construct(
public string $message = 'Este campo não pode estar vazio'
) {}
}
Os alvos disponíveis incluem:
- TARGET_CLASS — classes, interfaces, traits, enums
- TARGET_METHOD — métodos
- TARGET_PROPERTY — propriedades
- TARGET_FUNCTION — funções
- TARGET_PARAMETER — parâmetros
- TARGET_ALL — qualquer elemento
Para permitir múltiplas instâncias do mesmo atributo, use Attribute::IS_REPEATABLE:
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
class ValidationRule {
public function __construct(public string $rule) {}
}
class User {
#[ValidationRule('required')]
#[ValidationRule('min:3')]
public string $username;
}
3. Atributos Nativos do PHP
O PHP fornece alguns atributos nativos úteis:
#[Attribute]— metaclasse para definir atributos customizados, como vimos acima.#[ReturnTypeWillChange]— usado para suprimir avisos de incompatibilidade de tipo de retorno ao implementar interfaces legadas (comoArrayAccess).#[Deprecated](PHP 8.3+) — marca elementos como obsoletos, gerando avisos em tempo de compilação.#[Override](PHP 8.3+) — garante que um método está sobrescrevendo um método de uma classe pai ou interface.#[SensitiveParameter]— oculta valores de parâmetros em stack traces, útil para senhas e tokens.
class LegacyCollection implements \ArrayAccess {
#[ReturnTypeWillChange]
public function offsetGet(mixed $offset): mixed {
return $this->data[$offset] ?? null;
}
}
#[Deprecated('Use novaClasse em vez disso')]
class OldService {}
class BaseController {
public function index(): void {}
}
class UserController extends BaseController {
#[Override]
public function index(): void {
// Se BaseController::index não existir, erro de compilação
}
}
function login(#[SensitiveParameter] string $password): void {
throw new \RuntimeException('Erro simulado');
}
4. Leitura e Reflexão de Atributos em Tempo de Execução
Atributos são lidos via reflexão usando a classe ReflectionAttribute. O processo envolve:
1. Obter um objeto de reflexão (ReflectionClass, ReflectionMethod, ReflectionProperty, etc.)
2. Chamar getAttributes() para obter um array de ReflectionAttribute
3. Usar newInstance() para instanciar o atributo com seus argumentos
class Product {
#[NotEmpty]
#[Email]
public string $email;
}
$reflection = new ReflectionProperty(Product::class, 'email');
$attributes = $reflection->getAttributes();
foreach ($attributes as $attribute) {
$instance = $attribute->newInstance();
echo get_class($instance) . "\n"; // NotEmpty, Email
}
Você pode filtrar por nome de classe específico:
$emailAttributes = $reflection->getAttributes(Email::class);
if (!empty($emailAttributes)) {
$emailAttr = $emailAttributes[0]->newInstance();
}
5. Casos de Uso Práticos com Atributos Customizados
Validação de dados
#[Attribute(Attribute::TARGET_PROPERTY)]
class Email {
public function __construct(public string $message = 'Email inválido') {}
}
class UserRegistration {
#[NotEmpty('Nome é obrigatório')]
public string $name;
#[Email('Forneça um email válido')]
public string $email;
}
function validate(object $obj): array {
$errors = [];
$reflection = new ReflectionClass($obj);
foreach ($reflection->getProperties() as $prop) {
foreach ($prop->getAttributes() as $attr) {
$validator = $attr->newInstance();
$value = $prop->getValue($obj);
if ($validator instanceof NotEmpty && empty($value)) {
$errors[$prop->getName()] = $validator->message;
}
if ($validator instanceof Email && !filter_var($value, FILTER_VALIDATE_EMAIL)) {
$errors[$prop->getName()] = $validator->message;
}
}
}
return $errors;
}
Roteamento em frameworks
#[Attribute(Attribute::TARGET_METHOD)]
class Route {
public function __construct(
public string $path,
public string $method = 'GET'
) {}
}
class ApiController {
#[Route('/users', 'GET')]
public function listUsers(): array {
return ['users' => []];
}
#[Route('/users', 'POST')]
public function createUser(): array {
return ['status' => 'created'];
}
}
Serialização
#[Attribute(Attribute::TARGET_PROPERTY)]
class JsonIgnore {}
class User {
public string $name;
public string $email;
#[JsonIgnore]
public string $password;
}
function toJson(object $obj): string {
$data = [];
$reflection = new ReflectionClass($obj);
foreach ($reflection->getProperties() as $prop) {
if (!empty($prop->getAttributes(JsonIgnore::class))) {
continue;
}
$data[$prop->getName()] = $prop->getValue($obj);
}
return json_encode($data);
}
6. Boas Práticas e Limitações
Quando usar atributos vs. interfaces vs. traits:
- Use interfaces para contratos comportamentais (o que uma classe faz).
- Use traits para reúso de implementação concreta.
- Use atributos para metadados declarativos que não alteram o comportamento da classe, apenas o descrevem.
Performance: Atributos são lidos exclusivamente via reflexão, que é um processo mais lento que chamadas diretas. Para aplicações de alto desempenho, considere cachear os resultados da leitura de atributos (ex.: em um container de injeção de dependência).
Limitações:
- Atributos não podem ser aplicados a closures, tipos escalares ou expressões.
- Não é possível usar atributos em variáveis locais ou parâmetros de tipos primitivos (apenas parâmetros de funções/métodos).
- A leitura de atributos requer reflexão, o que pode ser verboso.
Versionamento: Atributos foram introduzidos no PHP 8.0. #[Override] e #[Deprecated] chegaram no PHP 8.3. Mantenha compatibilidade com versões anteriores usando polyfills ou condicionais.
7. Comparação com Docblocks e Anotações de Bibliotecas
Vantagens dos atributos nativos:
- Tipagem forte: Argumentos são validados pelo PHP (tipos, valores padrão).
- Suporte de IDE: Autocomplete, refatoração, navegação.
- Performance: Parseados uma vez pela engine, sem necessidade de ler docblocks como strings.
- Semântica clara: Não há ambiguidade entre comentários e metadados.
Migração de anotações Doctrine para atributos nativos:
Código legado com docblocks:
/**
* @ORM\Entity(repositoryClass="UserRepository")
* @ORM\Table(name="users")
*/
class User {
/** @ORM\Id @ORM\GeneratedValue @ORM\Column(type="integer") */
private int $id;
}
Código moderno com atributos nativos:
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: UserRepository::class)]
#[ORM\Table(name: 'users')]
class User {
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private int $id;
}
A migração é direta e melhora a legibilidade, além de permitir que IDEs ofereçam melhor suporte.
8. Conclusão e Próximos Passos
Atributos nativos representam um avanço significativo na forma como metadados são declarados em PHP. Eles substituem docblocks com uma sintaxe formal, tipada e verificável pela engine, melhorando a manutenibilidade, a segurança e a integração com ferramentas.
Combinados com outros recursos modernos do PHP (como readonly, tipos union, enums e exceções tipadas), atributos permitem construir sistemas mais expressivos e auto-documentados. Frameworks como Symfony e Laravel já adotaram atributos nativos para roteamento, validação e injeção de dependência.
Para aprofundar, explore a documentação oficial, as RFCs originais e os tutoriais práticos listados nas referências. Experimente migrar pequenos trechos de código com docblocks para atributos e sinta a diferença na experiência de desenvolvimento.
Referências
- PHP Manual: Atributos — Documentação oficial completa sobre sintaxe, criação e uso de atributos nativos no PHP.
- PHP RFC: Attributes v2 — Proposta original que definiu a implementação dos atributos no PHP 8.0, com detalhes técnicos e discussões.
- PHP 8.0: Atributos (Anotações Nativas) — Artigo técnico do Stitcher.io explicando conceitos, exemplos práticos e diferenças para docblocks.
- Doctrine ORM: Migração para Atributos Nativos — Guia oficial do Doctrine sobre como usar atributos nativos para mapeamento objeto-relacional.
- PHP 8.3: Atributos Override e Deprecated — Notas de lançamento do PHP 8.3 destacando os novos atributos nativos
#[Override]e#[Deprecated]. - Symfony Routing com Atributos — Documentação do Symfony sobre como usar atributos para definir rotas em controllers.
- Laravel 11: Atributos de Validação — Exemplo de como atributos podem ser usados para validação no ecossistema Laravel.