Atributos PHP 8.3. Como funcionam e a sua Utilização no Mundo Real

Entre as muitas novas funcionalidades os atributos PHP foram uma das mais promissoras que encontrei no PHP 8. Os Atributos no PHP 8 foram revistos diversas vezes antes de se estabelecerem na implementação atual, a qual iremos aprofundar um pouco neste artigo.
Tabela de Conteúdos
O que são Atributos PHP?
Essencialmente, Atributos são tipos especiais de classes que podem ser usados para adicionar metadados a outras classes, propriedades, funções, métodos, parâmetros e constantes.
Antes do PHP 8, era prática comum usar “doc-comments” ou “anotações” para adicionar metadados a essas entidades. Por exemplo, aqui está como pode definir um DocBlock de método com metadados que indicam o tipo dos argumentos e o tipo de retorno.
/**
* Descrição do método aqui.
*
* @param Random $mathRandom
* @param StdlibDateTime $dateTime
* @param int $number
*
* @return int
*/
private function doSomething(
Random $mathRandom,
StdlibDateTime $dateTime,
int $number
) : int {
}
Estas anotações de comentários são apenas strings e são usados para manter informações estruturadas, e algumas bibliotecas usam as anotações para gerar documentação completa analisando-os, como é o caso do Package L5-Swagger da Darkonline. Além disso, IDEs como o PhpStorm utilizam esses DocBlocks para mostrar diagnósticos de forma inteligente e mostrar avisos e erros em tempo de execução no código, se houver algum.
A maneira de fazer tudo isso nativamente estava ausente, e portanto, os Atributos foram introduzidos no PHP 8.0 para fornecer a capacidade de definir metadados nativamente.
Vamos verificar como você pode definir atributos.
Como Definir Atributos PHP?
Na sua forma mais simples, um atributo PHP pode ser aplicado usando a sintaxe #[attr].
🎯 Curiosidade: A sintaxe de atributos do PHP é inspirada na sintaxe de Atributos do Rust.
Então, se quiser aplicar um atributo a uma classe, por exemplo, pode fazer assim.
#[Attribute]
final class TestClass {
}
Os atributos também podem ser aplicados a várias outras coisas, como propriedades, funções, métodos, parâmetros e constantes, assim.
#[ExampleAttribute]
class Foo
{
#[ExampleAttribute]
public const FOO = 'foo';
#[ExampleAttribute]
public $x;
#[ExampleAttribute]
public function foo(#[ExampleAttribute] $bar) { }
}
$object = new #[ExampleAttribute] class () { };
#[ExampleAttribute]
function f1() { }
$f2 = #[ExampleAttribute] function () { };
$f3 = #[ExampleAttribute] fn () => 1;
Uso Prático
Agora, vamos dar uma olhada em como você pode usar Atributos para recuperar metadados quando aplicados a uma classe. Para isso, você precisará criar um atributo personalizado assim.
Como pode ver, a classe TestAttribute é apenas uma classe regular à qual aplicamos um atributo global usando #[\Attribute]. Para receber metadados, podemos definir um construtor que será usado para recuperar os metadados posteriormente.
#[\Attribute]
class TestAttribute
{
public function __construct(public string $testArgument)
{}
}
Uma vez criado, podemos aplicar este atributo personalizado a outra classe assim.
#[TestAttribute('Olá Mundo')]
class MinhaClasse
{
}
Como pode ver, podemos aplicar o atributo personalizado da mesma forma que o atributo global que aplicamos anteriormente. E como pode perceber, também passamos o argumento que servirá como metadado para esta classe.
Agora é hora de recuperar os metadados desta classe. E como faremos? É relativamente simples.
Tudo que você precisa fazer é usar a ReflectionClass incorporada para obter os detalhes sobre MinhaClasse assim.
$reflection = new \ReflectionClass(MinhaClasse::class);
$classAttributes = $reflection->getAttributes();
print_r($classAttributes[0]->newInstance()->testArgument);
Vamos desmembrar isso. Como pode ver, primeiro obtivemos a instância de ReflectionClass passando MinhaClasse como argumento.
Feito isso, podemos obter todos os atributos aplicados a esta classe chamando o método getAttributes.
Isso retornará um array de todos os atributos aplicados à classe. Então, se você imprimir $classAttributes, parecerá assim.
array:1 [▼
0 => ReflectionAttribute {#1829}
]
Em seguida, para obter o atributo da classe, chamamos o método newInstance() no atributo que retornará a instância do TestAttribute e a partir daí, pode recuperar os metadados chamando o argumento assim.
$classAttributes[0]->newInstance()->testArgument // "Olá Mundo"
Âmbitos de Atributos
Além de aplicar atributos a diferentes entidades em PHP, também é possível definir o âmbito onde um atributo em particular deve ser utilizado.
Para isso, é necessário passar sinalizadores especiais para o Atributo, que incluem o seguinte: Attribute::TARGET_METHOD, Attribute::TARGET_CLASS, Attribute::TARGET_FUNCTION, Attribute::TARGET_PROPERTY, Attribute::TARGET_CLASS_CONSTANT, Attribute::TARGET_PARAMETER e Attribute::TARGET_ALL.
Portanto, se eu quiser limitar um atributo personalizado a ser aplicado apenas a classes PHP, eu faria da seguinte forma.
#[\Attribute(Attribute::TARGET_CLASS)]
class TestAttribute
{
public function __construct(public string $testArgument)
{}
}
Agora, se eu tentar aplicar este atributo a um método de classe e obter os metadados por meio de reflexão, como abaixo…
class MyClass
{
#[TestAttribute('Olá Mundo')]
public function index()
{
}
}
$reflection = new \ReflectionMethod(MyClass::class, 'index');
$methodAttributes = $reflection->getAttributes();
print_r($methodAttributes[0]->newInstance()->testArgument);
… o PHP lançará um erro fatal algo como o abaixo.
Error Attribute “TestAttribute” cannot target method (allowed targets: class)
Como pode ser visto, o erro é autoexplicativo. Ou seja, não é possível aplicar o atributo em métodos de classe, pois ele é projetado para ser usado em classes.
Uso no Mundo Real de Atributos PHP
Enquanto o exemplo anterior parece bom para entender o conceito de Atributos php, a equipa da Spatie encontrou uma maneira interessante de usar Atributos num cenário do mundo real no seu package laravel-route-attributes.
O que essencialmente este pacote faz é fornecer anotações para registrar automaticamente rotas em aplicações Laravel em execução no PHP 8.
Você pode registrar rotas diretamente aplicando um atributo à ação do controlador assim.
use Spatie\RouteAttributes\Attributes\Get;
class MyController
{
#[Get('minha-rota')]
public function meuMetodo()
{
}
}
Este atributo registrará automaticamente esta rota:
Route::get('minha-rota', [MyController::class, 'meuMetodo']);
Como pode ser visto, o atributo Spatie\RouteAttributes\Attributes\Get aplicado aqui pode ser usado para registrar a rota de obtenção. Existem outros atributos para o restante dos verbos HTTP que você pode usar assim.
#[Spatie\RouteAttributes\Attributes\Post('minha-uri')]
#[Spatie\RouteAttributes\Attributes\Put('minha-uri')]
#[Spatie\RouteAttributes\Attributes\Patch('minha-uri')]
#[Spatie\RouteAttributes\Attributes\Delete('minha-uri')]
#[Spatie\RouteAttributes\Attributes\Options('minha-uri')]
Pode ainda usar o metodo apiResource para gerar automaticamente as rotas em todos os metodos predefinidos pelo laravel tal como o exemplo seguinte:
use Spatie\RouteAttributes\Attributes\Resource;
#[Prefix('api/v1')]
#[Resource(
resource: 'photos.comments',
apiResource: true,
shallow: true,
parameters: ['comments' => 'comment:uuid'],
names: 'api.v1.photoComments',
except: ['destroy'],
)]
// OR #[ApiResource(resource: 'photos.comments', shallow: true, ...)]
class PhotoCommentController
{
public function index(Photo $photo)
{
}
public function store(Request $request, Photo $photo)
{
}
public function show(Comment $comment)
{
}
public function update(Request $request, Comment $comment)
{
}
}
Podemos usar com metodos customizados:
use Spatie\RouteAttributes\Attributes\Get;
class MyController
{
#[Get('my-route', name: "my-route-name")]
public function myMethod()
{
}
}
Ou ainda adicionar os nossos middlewares:
use Spatie\RouteAttributes\Attributes\Get;
class MyController
{
#[Get('my-route', middleware: MyMiddleware::class)]
public function myMethod()
{
}
}

Conclusão:
Os Atributos PHP 8.3 representam uma adição significativa ao arsenal de recursos dos programadores PHP, oferecendo uma maneira mais eficaz e nativa de adicionar metadados a diferentes partes do código. Ao contrário das práticas tradicionais de “doc-comments”, os Atributos proporcionam uma abordagem mais limpa e estruturada para enriquecer o código com informações adicionais.
Baseado em : https://www.amitmerchant.com/how-to-use-php-80-attributes/