PHP 8.0: Atributos (3/4)
La versión 8.0 de PHP está siendo liberada ahora mismo. Está llena de cosas nuevas como ninguna otra versión antes. Su introducción mereció cuatro artículos separados. En la tercera parte echaremos un vistazo a los atributos.
Los atributos proporcionan una forma completamente nueva de escribir metadatos estructurados para clases y todos sus miembros, así como funciones, cierres y sus parámetros. Los comentarios PhpDoc se han utilizado para este propósito hasta ahora, pero su sintaxis siempre ha sido tan floja e inconsistente que no era posible empezar a procesarlos maquinalmente. Por ello, están siendo sustituidos por atributos con sintaxis determinada y soporte en clases de reflexión.
Debido a esto, las bibliotecas que previamente han recuperado metadatos
parseando comentarios phpDoc podrán reemplazarlos por atributos. Un ejemplo es
Nette, donde en las últimas versiones de Application y DI ya se pueden utilizar
los atributos Persistent
, CrossOrigin
y
Inject
en lugar de las anotaciones @persistent
,
@crossOrigin
y @inject
.
Código que utiliza anotaciones:
/**
* @persistent(comp1, comp2)
*/
class SomePresenter
{
/** @persistent */
public $page = 1;
/** @inject */
public Facade $facade;
/**
* @crossOrigin
*/
public function handleSomething()
{
}
}
Lo mismo con los 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 igual que si fueran clases, en el
contexto de los espacios de nombres y las cláusulas use
. Así que
incluso sería posible escribirlos, por ejemplo, de la siguiente manera:
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 es definitivamente mejor si existe, porque entonces el editor puede sugerirla durante la escritura, el analizador estático reconoce errores tipográficos, etc.
Sintaxis
Es inteligente que PHP antes de la versión 8 vea los atributos sólo como comentarios, por lo que también pueden ser usados en código que debería funcionar en versiones anteriores.
La sintaxis de un atributo individual se parece a crear una instancia de
objeto si omitimos el operador new
. Entonces, el nombre de la clase
seguido de argumentos opcionales 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 pueden ser escritos individualmente o separados por una coma:
#[Inject]
#[Lazy]
public Foo $foo;
#[Inject, Lazy]
public Bar $bar;
El siguiente atributo se aplica a las tres propiedades:
#[Common]
private $a, $b, $c;
Las expresiones y constantes simples, que pueden evaluarse durante la compilación y se utilizan como valores por defecto para las propiedades, pueden utilizarse como argumentos en 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),
]
Lamentablemente, el valor de un argumento no puede ser otro atributo, es decir, los atributos no pueden anidarse. Por ejemplo, no existe una forma directa de convertir en atributos la siguiente anotación utilizada en Doctrine:
/**
* @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.
Reflejo del atributo
Qué atributos tienen los elementos individuales se puede determinar mediante
el uso de la reflexión. Las clases de reflexión tienen un nuevo método
getAttributes()
, que devuelve una matriz de objetos
ReflectionAttribute
.
use MyAttributes\Example;
#[Example('Hello', 123)]
class Foo
{}
$reflection = new ReflectionClass(Foo::class);
foreach ($reflection->getAttributes() as $attribute) {
$attribute->getName(); // full attribute name, e.g. MyAttributes\Example
$attribute->getArguments(); // ['Hello', 123]
$attribute->newInstance(); // returns an instance: new MyAttributes\Example('Hello', 123)
}
Los atributos devueltos pueden filtrarse mediante un parámetro, por ejemplo,
$reflection->getAttributes(Example::class)
devuelve sólo
atributos Example
.
Clases de atributos
La clase de atributo MyAttributes\Example
puede no existir.
Sólo una llamada a un método newInstance()
requiere su existencia
porque la instancia. Así que vamos a escribirla. Será una clase completamente
ordinaria, sólo tenemos que proporcionar el atributo Attribute
(es
decir, del espacio de nombres del sistema global):
namespace MyAttributes;
use Attribute;
#[Attribute]
class Example
{
public function __construct(string $message, int $number)
{
...
}
}
Se puede restringir para qué elementos del lenguaje se permitirá el uso del atributo. Por ejemplo, sólo para clases y propiedades:
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_PROPERTY)]
class Example
{
...
}
Banderas TARGET_CLASS
, TARGET_FUNCTION
,
TARGET_METHOD
, TARGET_PROPERTY
,
TARGET_CLASS_CONSTANT
, TARGET_PARAMETER
y por defecto
TARGET_ALL
están disponibles.
Pero cuidado, la verificación del uso correcto o incorrecto se produce
sorprendentemente sólo cuando se llama al método newInstance()
.
El propio compilador no realiza esta comprobació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 ha estado usando anotaciones desde su primera versión para indicar parámetros y componentes persistentes, no se han usado más masivamente porque no eran una construcción nativa del lenguaje, por lo que los editores no las sugerían y era fácil cometer un error. Aunque esto ya se está solucionando con plugins del editor, la forma realmente nativa que están aportando los atributos abre posibilidades completamente nuevas.
Por cierto, los atributos han obtenido una excepción en el estándar de
codificación Nette, que exige que el nombre de la clase, además de
especificidad (por ejemplo, Product
, InvalidValue
),
contenga también una generalidad (por ejemplo, ProductPresenter
,
InvalidValueException
). De lo contrario, al utilizarlo en el
código, no quedaría claro qué representa exactamente la clase. En el caso de
los atributos, esto no es deseable, por lo que la clase se denomina
Inject
en lugar de InjectAttribute
.
En la última parte, veremos qué nuevas funciones y clases han aparecido en PHP e introduciremos el Compilador Just in Time.
Para enviar un comentario, inicie sesión