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