Leccion 44 de 75 10 min de lectura

Attributes (Atributos)

Los Attributes permiten agregar metadatos estructurados a clases, metodos, propiedades y parametros. Se leen mediante Reflection y son utiles en frameworks y bibliotecas.

Sintaxis basica

Los attributes se declaran con #[NombreAtributo] justo antes del elemento que decoran:

PHP
<?php

declare(strict_types=1);

// Definir un attribute
#[\Attribute]
class Route
{
    public function __construct(
        public string $path,
        public string $method = 'GET'
    ) {}
}

// Usar el attribute
#[Route('/usuarios', 'GET')]
class UsuarioController
{
    #[Route('/usuarios/{id}', 'GET')]
    public function mostrar(int $id): void
    {
        // ...
    }

    #[Route('/usuarios', 'POST')]
    public function crear(): void
    {
        // ...
    }
}

Leer attributes con Reflection

Los attributes no hacen nada por si solos. Se leen usando la API de Reflection:

PHP
<?php

declare(strict_types=1);

#[\Attribute]
class Route
{
    public function __construct(
        public string $path,
        public string $method = 'GET'
    ) {}
}

#[Route('/api/productos')]
class ProductoController {}

// Leer el attribute de la clase
$reflector = new \ReflectionClass(ProductoController::class);
$attributes = $reflector->getAttributes(Route::class);

foreach ($attributes as $attribute) {
    $route = $attribute->newInstance(); // Crea instancia del attribute
    echo "Path: {$route->path}\n";      // /api/productos
    echo "Method: {$route->method}\n";  // GET
}

Attributes en propiedades

PHP
<?php

declare(strict_types=1);

#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Column
{
    public function __construct(
        public string $name,
        public string $type = 'string'
    ) {}
}

class Usuario
{
    #[Column('user_id', 'int')]
    public int $id;

    #[Column('user_name')]
    public string $nombre;

    #[Column('user_email')]
    public string $email;
}

// Leer attributes de propiedades
$reflector = new \ReflectionClass(Usuario::class);

foreach ($reflector->getProperties() as $propiedad) {
    $attrs = $propiedad->getAttributes(Column::class);
    if (!empty($attrs)) {
        $column = $attrs[0]->newInstance();
        echo "{$propiedad->getName()} -> {$column->name} ({$column->type})\n";
    }
}
// id -> user_id (int)
// nombre -> user_name (string)
// email -> user_email (string)

Targets: donde aplicar attributes

Puedes restringir donde se puede usar un attribute:

PHP
<?php

declare(strict_types=1);

// Solo en clases
#[\Attribute(\Attribute::TARGET_CLASS)]
class Entity {}

// Solo en metodos
#[\Attribute(\Attribute::TARGET_METHOD)]
class Cache {}

// Solo en propiedades
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Validate {}

// En multiples lugares
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_FUNCTION)]
class Deprecated {}

// Permitir repetir el attribute
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
class Tag
{
    public function __construct(public string $nombre) {}
}

#[Tag('orm')]
#[Tag('entity')]
class Producto {}

Ejemplo practico: Validacion

PHP
<?php

declare(strict_types=1);

#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Required {}

#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Email {}

#[\Attribute(\Attribute::TARGET_PROPERTY)]
class MinLength
{
    public function __construct(public int $min) {}
}

class RegistroDTO
{
    #[Required]
    #[MinLength(3)]
    public string $nombre;

    #[Required]
    #[Email]
    public string $email;

    #[Required]
    #[MinLength(8)]
    public string $password;
}

// Validador simple
function validar(object $objeto): array
{
    $errores = [];
    $reflector = new \ReflectionClass($objeto);

    foreach ($reflector->getProperties() as $prop) {
        $prop->setAccessible(true);
        $valor = $prop->getValue($objeto);
        $nombre = $prop->getName();

        // Required
        if ($prop->getAttributes(Required::class)) {
            if (empty($valor)) {
                $errores[] = "$nombre es requerido";
            }
        }

        // Email
        if ($prop->getAttributes(Email::class)) {
            if (!filter_var($valor, FILTER_VALIDATE_EMAIL)) {
                $errores[] = "$nombre debe ser un email valido";
            }
        }

        // MinLength
        $minAttrs = $prop->getAttributes(MinLength::class);
        if (!empty($minAttrs)) {
            $min = $minAttrs[0]->newInstance()->min;
            if (strlen($valor) < $min) {
                $errores[] = "$nombre debe tener al menos $min caracteres";
            }
        }
    }

    return $errores;
}

$dto = new RegistroDTO();
$dto->nombre = 'Jo';
$dto->email = 'no-es-email';
$dto->password = '123';

print_r(validar($dto));
// ['nombre debe tener al menos 3 caracteres',
//  'email debe ser un email valido',
//  'password debe tener al menos 8 caracteres']

Ejercicios

Ejercicio 1: Attribute Deprecated

Crea un attribute Deprecated que reciba un mensaje y usalo para marcar un metodo como obsoleto.

Ver solucion
PHP
<?php

declare(strict_types=1);

#[\Attribute(\Attribute::TARGET_METHOD)]
class Deprecated
{
    public function __construct(
        public string $mensaje = 'Este metodo esta obsoleto'
    ) {}
}

class Calculadora
{
    #[Deprecated('Usa sumar() en su lugar')]
    public function add(int $a, int $b): int
    {
        return $a + $b;
    }

    public function sumar(int $a, int $b): int
    {
        return $a + $b;
    }
}

// Verificar si un metodo esta deprecated
$ref = new \ReflectionMethod(Calculadora::class, 'add');
$attrs = $ref->getAttributes(Deprecated::class);

if (!empty($attrs)) {
    $dep = $attrs[0]->newInstance();
    echo "Aviso: {$dep->mensaje}";
}

Ejercicio 2: Attribute JsonProperty

Crea un attribute JsonProperty que mapee propiedades a nombres JSON diferentes.

Ver solucion
PHP
<?php

declare(strict_types=1);

#[\Attribute(\Attribute::TARGET_PROPERTY)]
class JsonProperty
{
    public function __construct(public string $name) {}
}

class Usuario
{
    #[JsonProperty('first_name')]
    public string $nombre;

    #[JsonProperty('last_name')]
    public string $apellido;

    public string $email; // Sin attribute, usa nombre original
}

function toJson(object $obj): string
{
    $data = [];
    $ref = new \ReflectionClass($obj);

    foreach ($ref->getProperties() as $prop) {
        $prop->setAccessible(true);
        $attrs = $prop->getAttributes(JsonProperty::class);
        $key = !empty($attrs)
            ? $attrs[0]->newInstance()->name
            : $prop->getName();
        $data[$key] = $prop->getValue($obj);
    }

    return json_encode($data);
}

$u = new Usuario();
$u->nombre = 'Ana';
$u->apellido = 'Garcia';
$u->email = 'ana@example.com';

echo toJson($u);
// {"first_name":"Ana","last_name":"Garcia","email":"ana@example.com"}

¿Te está gustando el curso?

Cursos premium con proyectos reales y soporte.

Descubrir cursos premium