Aprendiendo a usar los Atributos en PHP

portada post "atributos en PHP y Drupal"
Solucionex
07
Jul 26

Este documento se ha realizado en base a la documentación oficial de PHP, Symfony y Drupal.

¿Qué son?

Los atributos de PHP ofrecen metadatos estructurados e interpretables y se pueden usar a nivel de clases, métodos, funciones, parámetros, etc. Tienen la gran ventaja de que pueden ser inspeccionados en tiempo de ejecución real a través de la API de Reflection, lo que permite un comportamiento dinámico sin modificar el código. La gran ventaja de los atributos es que proporcionan una forma declarativa de anotar el código con metadatos.

Dicho de otra forma, un atributo no cambia por sí mismo lo que hace una clase o un método. Lo que hace es aportar información para que otra parte del sistema pueda interpretarla. Los atributos sustituyen principalmente a los metadatos que antes se escribían como anotaciones o como configuración externa.

Por ejemplo, en caso de un sistema Symfony podríamos hacer lo siguiente:

#[Route('/blog', name: 'blog_list')]
public function list(): Response
{
    // ...
}

En este caso, el método list() no sabe enrutar una petición por sí solo. El atributo #[Route] añade metadatos para que Symfony pueda detectar que ese método debe ejecutarse cuando llegue una petición a /blog.

¿Qué problema resuelven?

Antes de los atributos, era habitual que esta información se definiera en tres sitios distintos:

En comentarios, mediante anotaciones:

/**
 * @Route("/blog", name="blog_list")
 */
public function list(): Response
{
    // ...
}

En archivos YAML, XML o PHP:

blog_list:
  path: /blog
  controller: App\Controller\BlogController::list

O mediante convenciones propias del framework.

Esto funcionaba, pero tenía algunos problemas. Las anotaciones vivían dentro de comentarios, por lo que PHP no las entendía realmente como parte del lenguaje. Eran los frameworks o librerías externas quienes tenían que parsear esos comentarios y convertirlos en información útil. Además, al mover o renombrar clases, métodos o propiedades, era más fácil que parte de la configuración quedara desactualizada.

Con los atributos, esa información pasa a formar parte de la sintaxis del lenguaje. La configuración sigue siendo declarativa, pero ahora es PHP quien entiende su estructura.

¿Cómo se implementan?

La sintáxis es sencilla: #[ATRIBUTO]. Dentro se indica el nombre del atributo y, opcionalmente, sus argumentos.

#[Route('/posts/{id}', name: 'post_show', methods: ['GET'])]
#[Cache(maxAge: 3600)]
public function show(int $id): Response
{
    // ...
}

¿Cómo se crean?

Para definir un atributo se puede hacer mediante una clase PHP. Se declara una clase y se marca con #[Attribute]. Es recomendable crear una clase para cada atributo.

use Attribute;

#[Attribute(Attribute::TARGET_METHOD)]
final class RequiresPermission
{
    public function __construct(
        public string $permission,
    ) {}
}

Después se puede usar sobre un método:

#[RequiresPermission('administer site configuration')]
public function settings(): Response
{
    // ...
}

Por sí solo, este atributo no comprueba permisos. Para que tenga efecto, tiene que existir una parte del sistema que lea ese atributo y actúe en consecuencia.

¿Cómo los lee el sistema?

Los atributos se leen mediante Reflection. PHP permite obtener los atributos definidos  usando métodos como getAttributes(). El resultado contiene el nombre del atributo, sus argumentos y permite crear una instancia del atributo cuando sea necesario.

Un ejemplo simplificado sería:

$reflection = new ReflectionMethod(PostController::class, 'show');

foreach ($reflection->getAttributes(RequiresPermission::class) as $attribute) {
    $permission = $attribute->newInstance()->permission;
}

En una aplicación real, normalmente no se hace a mano, ya que para esto están los frameworks; Symfony, Drupal, Doctrine o cualquier librería que soporte atributos puede escanear el código, leer los metadatos y construir sus mapas internos.

Los atributos en Drupal

En Drupal se puede ver un ejemplo muy claro con los Plugins, ya que un plugin necesita describirse y ser reconocido por el sistema. En versiones anteriores de PHP mucha de esa información se escribía mediante anotaciones en el DocBlock de la clase. Con atributos, esos metadatos puede pasar a estar definida de forma nativa.

Un ejemplo típico de bloque en Drupal sería:

use Drupal\Core\Block\Attribute\Block;
use Drupal\Core\StringTranslation\TranslatableMarkup;

#[Block(
    id: 'example_block',
    admin_label: new TranslatableMarkup('Example block'),
    category: new TranslatableMarkup('Custom')
)]
final class ExampleBlock extends BlockBase
{
    // ...
}

Drupal dispone de clases de atributo específicas, como Drupal\Core\Block\Attribute\Block, que permiten describir plugins de tipo bloque mediante argumentos como el ID del plugin, la etiqueta administrativa o la categoría.

Ventajas

La principal ventaja de los atributos es la claridad. La información queda más cerca del elemento al que afecta, no está fragmentado y es mucho más semántico.

También hay una mejora en mantenimiento. Si se renombra una clase o se mueve un método, es más fácil detectar el impacto cuando la configuración está junto al código.

Otra ventaja importante es que los atributos son código PHP real. Se pueden importar con use, pueden tener argumentos tipados, pueden usar constantes y pueden ser validados de forma más robusta por las herramientas.

Además, reducen la dependencia de comentarios ya que los comentarios deberían explicar el código, no comportarse como configuración ejecutable.

En definitiva: Los atributos en PHP no son simplemente una nueva forma de escribir anotaciones. Son una manera nativa, estructurada y más segura de añadir metatados al código.

Fuentes para este post:

La imagen de este post ha sido generada con Inteligencia Artifical. 

php
Drupal 11
Drupal