PHP 8.0: Atributos (3/4)
A versão 8.0 do PHP foi lançada. Está tão cheia de novidades como nenhuma versão anterior. A apresentação delas exigiu quatro artigos separados. Neste terceiro, veremos os atributos.
Os atributos trazem uma maneira completamente nova de escrever metadados estruturados para classes e todos os seus membros, bem como para funções, closures e seus parâmetros. Para este fim, até agora eram usados comentários phpDoc, mas sua sintaxe sempre foi tão livre e inconsistente que não era possível começar a processá-los automaticamente. Por isso, são substituídos por atributos com sintaxe fixa e suporte em classes de reflexão.
Portanto, bibliotecas que até agora obtinham metadados analisando
comentários phpDoc poderão substituí-los por atributos. Um exemplo é
o Nette, onde nas versões mais recentes do Application e DI, você já pode
usar os atributos Persistent, CrossOrigin e
Inject em vez das anotações @persistent,
@crossOrigin e @inject.
Código usando anotações:
/**
* @persistent(comp1, comp2)
*/
class SomePresenter
{
/** @persistent */
public $page = 1;
/** @inject */
public Facade $facade;
/**
* @crossOrigin
*/
public function handleSomething()
{
}
}
O mesmo usando atributos:
use Nette\Application\Attributes\CrossOrigin;
use Nette\Application\Attributes\Persistent;
use Nette\DI\Attributes\Inject;
#[Persistent('comp1', 'comp2')]
class SomePresenter
{
#[Persistent]
public int $page = 1;
#[Inject]
public Facade $facade;
#[CrossOrigin]
public function handleSomething()
{
}
}
O PHP avalia os nomes dos atributos da mesma forma que se fossem classes, ou
seja, no contexto do namespace e das cláusulas use. Assim, seria
possível escrevê-los, por exemplo, desta forma:
use Nette\Application\Attributes;
class SomePresenter
{
#[Attributes\Persistent]
public int $page = 1;
#[\Nette\DI\Attributes\Inject]
public Facade $facade;
A classe que representa o atributo pode existir ou não. Mas é certamente melhor que exista, porque então o editor pode sugeri-la ao escrever, o analisador estático reconhece erros de digitação, etc.
Sintaxe
O que é útil é que o PHP antes da versão 8 vê os atributos apenas como comentários, então eles podem ser usados mesmo em código que deve funcionar em versões mais antigas.
A notação de um único atributo parece a criação de uma instância de
objeto, se omitirmos o operador new. Ou seja, o nome da classe
seguido por argumentos entre parênteses:
#[Column('string', 32, true, false)]#
protected $username;
E aqui é o lugar onde o novo recurso quente do PHP 8.0 pode ser colocado em uso – argumentos nomeados:
#[Column(
type: 'string',
length: 32,
unique: true,
nullable: false,
)]#
protected $username;
Cada elemento pode ter vários atributos, que podem ser escritos separadamente ou separados por vírgula:
#[Inject]
#[Lazy]
public Foo $foo;
#[Inject, Lazy]
public Bar $bar;
O seguinte atributo se aplica a todas as três propriedades:
#[Common]
private $a, $b, $c;
Nos valores padrão das propriedades, podem ser usadas expressões simples e constantes que podem ser avaliadas durante a compilação, e o mesmo se aplica aos argumentos dos atributos:
#[
ScalarExpression(1 + 1),
ClassNameAndConstants(PDO::class, PHP_VERSION_ID),
BitShift(4 >> 1, 4 << 1),
BitLogic(1 | 2, JSON_HEX_TAG | JSON_HEX_APOS),
]
Infelizmente, o valor de um argumento não pode ser outro atributo, ou seja, não é possível aninhar atributos. Por exemplo, a seguinte anotação usada no Doctrine não pode ser transformada diretamente em atributos:
/**
* @Table(name="products",uniqueConstraints={@UniqueConstraint(columns={"name", "email"})})
*/
Além disso, não há atributo equivalente para o arquivo phpDoc, ou seja, um comentário localizado no início de um arquivo, que é usado, por exemplo, pelo Nette Tester.
Obtendo atributos
Descobrimos quais atributos os elementos individuais possuem usando
reflexão. As classes de reflexão possuem um novo método
getAttributes(), que retorna um array de objetos
ReflectionAttribute.
use MyAttributes\Example;
#[Example('Hello', 123)]
class Foo
{}
$reflection = new ReflectionClass(Foo::class);
foreach ($reflection->getAttributes() as $attribute) {
$attribute->getName(); // nome completo do atributo, ex: MyAttributes\Example
$attribute->getArguments(); // ['Hello', 123]
$attribute->newInstance(); // retorna a instância new MyAttributes\Example('Hello', 123)
}
Os atributos retornados podem ser filtrados por um parâmetro, por exemplo,
$reflection->getAttributes(Example::class) retornará apenas os
atributos Example.
Classes de atributos
A classe de atributo MyAttributes\Example não precisa existir.
A existência é exigida apenas pela chamada do método
newInstance(), porque cria sua instância. Vamos então
escrevê-la. Será uma classe completamente normal, apenas precisamos indicar
o atributo Attribute nela (ou seja, do namespace global do
sistema):
namespace MyAttributes;
use Attribute;
#[Attribute]
class Example
{
public function __construct(string $message, int $number)
{
...
}
}
É possível restringir em quais elementos da linguagem será legal usar o atributo. Assim, por exemplo, apenas em classes e propriedades:
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_PROPERTY)]
class Example
{
...
}
Estão disponíveis as flags TARGET_CLASS,
TARGET_FUNCTION, TARGET_METHOD,
TARGET_PROPERTY, TARGET_CLASS_CONSTANT,
TARGET_PARAMETER e o padrão TARGET_ALL.
Mas atenção, a verificação do uso correto ou incorreto ocorre
surpreendentemente apenas na chamada do método newInstance().
O próprio compilador não realiza essa verificação.
O futuro com atributos
Graças aos atributos e aos novos tipos, os
comentários de documentação do PHP se tornarão, pela primeira vez em sua
história, realmente apenas comentários de documentação. O PhpStorm já
está vindo com seus
próprios atributos, que podem substituir, por exemplo, a anotação
@deprecated. E pode-se presumir que este atributo será padrão no
PHP um dia. Da mesma forma, outras anotações serão substituídas, como
@throws, etc.
Embora o Nette use anotações desde sua primeira versão para marcar parâmetros e componentes persistentes, seu uso mais massivo não ocorreu porque não era uma construção nativa da linguagem, então os editores não as sugeriam e era fácil cometer erros nelas. Embora isso seja resolvido hoje por plugins de editor, o caminho verdadeiramente nativo que os atributos trazem abre possibilidades completamente novas.
A propósito, os atributos receberam uma exceção no Nette Coding Standard,
que exige que o nome da classe, além da especificidade (por exemplo,
Product, InvalidValue), também contenha generalidade
(ou seja, ProductPresenter, InvalidValueException).
Caso contrário, não ficaria claro no uso no código o que exatamente a classe
representa. Para atributos, isso, por outro lado, não é desejável, então a
classe se chama Inject em vez de InjectAttribute.
Na última parte, veremos quais novas funções e classes apareceram no PHP e apresentaremos o Just in Time Compiler.
Faça o login para enviar um comentário