Uno de los cambios más grandes en PHP 8 es el soporte nativo de Atributos, los cuales ofrecen la habilidad de añadir información de meta-datos estructurada y legible para máquina en forma de declaraciones dentro del código. Tanto clases como métodos, funciones, parámetros, propiedades y constantes de clase admiten atributos.

Los meta-datos contenidos dentro de atributos pueden ser inspeccionados con Reflection APIs. Puedes pensar en los Atributos de PHP como un lenguaje de configuración incrustado directamente en el código.

Antes vs Después: Comentarios DocBlock

Aunque los comentarios DocBlocks han sido el método de elección para escribir documentación en PHP durante años, estos en realidad no son más que comentarios en los que aplicamos una sintaxis especial por convención, y que posteriormente tenemos que procesarlos con librerías externas para transformarlos en verdaderas estructuras de datos.

Esto significa que realmente no hay una convención definida para DocBlocks ni tampoco implementación estándar, es muy fácil cometer errores de sintaxis, o encontrarnos con errores debido a que algunas herramientas pueden usar reglas de procesamiento incompatibles entre sí.

Algunos IDEs como PhpStorm utilizan DocBlocks para mostrar diagnósticos, advertencias en tiempo de ejecución y errores en el código.

<?php // Ejemplo de un comentario DocBlocks

class PostsController
{
    /**
     * @Route("/api/posts/{id}", methods={"GET"})
     */
    public function get($id) { /* ... */ }
}

Atributos

Para corregir estos problemas PHP 8 introduce los atributos, una alternativa estandarizada y con soporte nativo a los comentarios. Los atributos tienen una sintaxis más compacta inspirada en Rust y en general son más poderosos que los comentarios DocBlock. Con los atributos podemos utilizar funciones avanzadas nativas del lenguaje, como herencia de clases, espacios de nombres y procesamiento con Reflection API.

Puedes utilizar tanto DocBlocks como Atributos simultáneamente.

<?php // Ejemplo de un Atributo PHP

class PostsController
{
    #[Route("/api/posts/{id}", methods: ["GET"])]
    public function get($id) { /* ... */ }
}

Sintaxis de Atributos

Usar Atributos es una alternativa elegante, estandarizada y con soporte nativo al uso de comentarios DocBlock. Aunque ambas alternativas pueden usarse de forma conjunta.

Declarando Atributos

Antes de aplicar un Atributo a otros elementos, primero debemos definir el Atributo como una clase declarada con el atributo raíz #[Attribute]

#[Attribute]
class FooAttr {
  // código
}

Los atributos de PHP son implementados como un tipo de clase especial. A diferencia de otras clases, PHP solamente valida atributos cuando son invocados, y no inmediatamente al interpretar el código.

Asignando Atributos a elementos de código

Después de declarar un nuevo atributo podemos asignarlo a otro elemento declarando la sintaxis #[Nombre], donde #[ y ] corresponden a las llaves de apertura y cierre, y Nombre corresponde al nombre del atributo.

Ejemplos:

Atributo

#[FooAttr]
function ejemplo()
{
  // código
}

Atributo con argumentos

Podemos pasar uno o más argumentos a cada atributo #[Nombre(argumento1)].

Solo se soportan argumentos con valores literales o expresiones de constante. Tanto la sintaxis posicional como la de argumentos con nombre son válidas para pasar argumentos.

#[FooAttr('foo', 'bar')]
function ejemplo()
{

}

Múltiples atributos por elemento

Se puede asignar más de un atributo por cada elemento.

(Declaración individual)

#[Foo]
#[Bar]
function ejemplo()
{
  // código
}

(Declaración agrupada)

#[Foo, Bar]
function ejemplo()
{
  // código
}

Atributos predefinidos de PHP

PHP incluye algunos atributos predefinidos para cubrir escenarios de uso comunes y como herramientas para fomentar mejores prácticas:

  • #[Deprecated] Marca un elemento como deprecado
  • #[Pure] Indica que una función no cambia de estado
  • #[JetBrains\PhpStorm\Immutable] Indica que un elemento es inmutable
  • #[Required] Indica que una propiedad es requerida por el constructor

Recuperando información de Atributos

Para recuperar información almacenada en atributos utilizaremos la Reflection API. Con esta API, podemos recuperar atributos como cadenas con el nombre del atributo y sus argumentos opcionales. Para esto haremos uso del método Reflection*::getAttributes() donde el * en Reflection* corresponde al nombre del tipo de elemento a ser referenciado, por ejemplo, para una clase, instanciaríamos el objeto ReflectionClass, para una función usaríamosReflectionFunction.

<?php

#[Attribute]
class FooAttr
{
  private string $mensaje;
  private int $numero;
		
  public function __construct(string $mensaje, int $numero)
  {
    $this->mensaje = $mensaje;
    $this->numero = $numero;
  }
}
	
#[FooAttr('foo', 124)]
function ejemplo()
{
  // Código
}
	
$reflector = new ReflectionFunction('ejemplo');
$atributos = $reflector->getAttributes();
	
foreach($atributos as $atributo) {
  $atributo->getName(); // Mis\Atributos\FooAttr
  $atributo->getArguments(); // ['foo', 123]
  $atributo->newInstance();
  // object(FooAttr)#1 (2) {
  //  ["mensaje":"Foo":private]=> string(3) "foo"        
  //  ["numero":"Foo":private]=> int(123) 
  // }
}

Retrocompatibilidad

Este cambio no es compatible con versiones anteriores a PHP 8.0, utilizar la sintaxis de Atributos puede generar errores graves en versiones previas.

Regístrate hoy en Styde y obtén acceso a todo nuestro contenido.

Lección anterior Compilación Just-In-Time en PHP 8 Lección siguiente Cambios en el manejo de errores en PHP 8