PHP 8.0: Atributos (3/4)
Ha salido la versión 8.0 de PHP. Está tan repleta de novedades como ninguna versión anterior. Su presentación ha requerido cuatro artículos separados. En este tercero, veremos los atributos.

Los atributos traen una forma completamente nueva de escribir metadatos estructurados para clases y todos sus miembros, así como para funciones, closures y sus parámetros. Para este propósito, hasta ahora se utilizaban comentarios phpDoc, pero su sintaxis siempre fue tan libre e inconsistente que no era posible empezar a procesarlos automáticamente. Por eso son reemplazados por atributos con sintaxis fija y soporte en clases de reflexión.
Por lo tanto, las librerías que hasta ahora obtenían metadatos parseando
comentarios phpDoc podrán reemplazarlos por atributos. Un ejemplo es Nette,
donde en las últimas versiones de Application y DI ya puede usar los atributos
Persistent
, CrossOrigin
e Inject
en lugar
de las anotaciones @persistent
, @crossOrigin
y
@inject
.
Código usando anotaciones:
/**
* @persistent(comp1, comp2)
*/
class SomePresenter
{
/** @persistent */
public $page = 1;
/** @inject */
public Facade $facade;
/**
* @crossOrigin
*/
public function handleSomething()
{
}
}
Lo mismo 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()
{
}
}
PHP evalúa los nombres de los atributos de la misma manera que si fueran
clases, es decir, en el contexto del espacio de nombres y las cláusulas
use
. Por lo tanto, sería posible escribirlos, por ejemplo,
también así:
use Nette\Application\Attributes;
class SomePresenter
{
#[Attributes\Persistent]
public int $page = 1;
#[\Nette\DI\Attributes\Inject]
public Facade $facade;
La clase que representa el atributo puede existir o no. Pero definitivamente es mejor si existe, porque entonces el editor puede sugerirla al escribir, el analizador estático puede detectar errores tipográficos, etc.
Sintaxis
Lo práctico es que PHP antes de la versión 8 ve los atributos solo como comentarios, por lo que se pueden usar también en código que debe funcionar en versiones anteriores.
La notación de un atributo individual parece la creación de una instancia
de objeto, si omitiéramos el operador new
. Es decir, el nombre de
la clase seguido opcionalmente por argumentos entre paréntesis:
#[Column('string', 32, true, false)]#
protected $username;
Y aquí es donde la nueva característica caliente de PHP 8.0 se puede poner en uso – argumentos con nombre:
#[Column(
type: 'string',
length: 32,
unique: true,
nullable: false,
)]#
protected $username;
Cada elemento puede tener múltiples atributos, que se pueden escribir por separado o separados por comas:
#[Inject]
#[Lazy]
public Foo $foo;
#[Inject, Lazy]
public Bar $bar;
El siguiente atributo se aplica a las tres propiedades:
#[Common]
private $a, $b, $c;
En los valores predeterminados de las propiedades se pueden usar expresiones simples y constantes que se pueden evaluar durante la compilación, y lo mismo se aplica a los argumentos de los atributos:
#[
ScalarExpression(1 + 1),
ClassNameAndConstants(PDO::class, PHP_VERSION_ID),
BitShift(4 >> 1, 4 << 1),
BitLogic(1 | 2, JSON_HEX_TAG | JSON_HEX_APOS),
]
Desafortunadamente, el valor de un argumento no puede ser otro atributo, es decir, no se pueden anidar atributos. Por ejemplo, la siguiente anotación utilizada en Doctrine no se puede transformar directamente en atributos de forma sencilla:
/**
* @Table(name="products",uniqueConstraints={@UniqueConstraint(columns={"name", "email"})})
*/
Tampoco existe un atributo equivalente para el archivo phpDoc, es decir, un comentario situado al principio de un archivo, que es utilizado, por ejemplo, por Nette Tester.
Obtención de atributos
Podemos averiguar qué atributos tienen los elementos individuales usando
reflexión. Las clases de reflexión disponen del nuevo método
getAttributes()
, que devuelve un array de objetos
ReflectionAttribute
.
use MyAttributes\Example;
#[Example('Hello', 123)]
class Foo
{}
$reflection = new ReflectionClass(Foo::class);
foreach ($reflection->getAttributes() as $attribute) {
$attribute->getName(); // nombre completo del atributo, p.ej. MyAttributes\Example
$attribute->getArguments(); // ['Hello', 123]
$attribute->newInstance(); // devuelve la instancia new MyAttributes\Example('Hello', 123)
}
Los atributos devueltos se pueden filtrar por parámetro, por ejemplo,
$reflection->getAttributes(Example::class)
devolverá solo los
atributos Example
.
Clases de atributos
La clase de atributo MyAttributes\Example
no tiene por qué
existir. Su existencia solo es requerida por la llamada al método
newInstance()
porque crea su instancia. Así que vamos a
escribirla. Será una clase completamente normal, solo que debemos indicar el
atributo Attribute
(es decir, del espacio de nombres global del
sistema):
namespace MyAttributes;
use Attribute;
#[Attribute]
class Example
{
public function __construct(string $message, int $number)
{
...
}
}
Se puede limitar a qué elementos del lenguaje será legal aplicar el atributo. Así, por ejemplo, solo a clases y propiedades:
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_PROPERTY)]
class Example
{
...
}
Están disponibles los flags TARGET_CLASS
,
TARGET_FUNCTION
, TARGET_METHOD
,
TARGET_PROPERTY
, TARGET_CLASS_CONSTANT
,
TARGET_PARAMETER
y el predeterminado TARGET_ALL
.
Pero cuidado, la verificación del uso correcto o incorrecto ocurre
sorprendentemente solo al llamar al método newInstance()
. El
propio compilador no realiza esta verificación.
El futuro con atributos
Gracias a los atributos y a los nuevos
tipos, los comentarios de la documentación PHP por primera vez en su
historia se convertirán realmente sólo en comentarios documentales. PhpStorm
ya viene con atributos
personalizados, que pueden reemplazar, por ejemplo, la anotación
@deprecated
. Y es de suponer que este atributo estará algún día
en PHP por defecto. Del mismo modo, otras anotaciones serán reemplazadas, como
@throws
etc.
Aunque Nette utiliza anotaciones desde su primera versión para marcar parámetros y componentes persistentes, su uso masivo no se produjo porque no era una construcción nativa del lenguaje, por lo que los editores no las sugerían y era fácil cometer errores en ellas. Aunque esto hoy en día lo solucionan los plugins para editores, el camino realmente nativo que traen los atributos abre posibilidades completamente nuevas.
Por cierto, los atributos obtuvieron una excepción en el Nette Coding
Standard, que requiere que el nombre de la clase, además de la especificidad
(por ejemplo, Product
, InvalidValue
), contenga
también la generalidad (es decir, ProductPresenter
,
InvalidValueException
). De lo contrario, no quedaría claro al
usarlo en el código qué representa exactamente la clase. En los atributos,
esto, por el contrario, no es deseable, por lo que la clase se llama
Inject
en lugar de InjectAttribute
.
En la última entrega veremos qué nuevas funciones y clases han aparecido en PHP y presentaremos el Just in Time Compiler.
Para enviar un comentario, inicie sesión