Explora el funcionamiento interno de los atributos, readonly, los callables de primera clase y el tipado estricto de PHP 8.
Abrir esta lección en KodokonLos atributos reemplazan las anotaciones de docblock por clases reales, validadas por el motor. Un atributo no es más que metadatos latentes: escribir #[Route('/users')] no crea ningún objeto. Solo cobra vida cuando algún código lo consulta mediante reflexión y llama a newInstance() - que es exactamente lo que hacen los frameworks al arrancar para construir sus tablas de enrutamiento. El destino permitido (TARGET_METHOD, TARGET_CLASS...) se declara a su vez mediante un atributo sobre la clase del atributo.
<?php
declare(strict_types=1);
#[Attribute(Attribute::TARGET_METHOD)]
final class Route
{
public function __construct(
public readonly string $path,
public readonly string $method = 'GET'
) {}
}
final class UserController
{
#[Route('/users', method: 'GET')]
public function index(): array
{
return ['users' => []];
}
}
$ref = new ReflectionMethod(
UserController::class,
'index'
);
foreach ($ref->getAttributes(Route::class) as $attr) {
$route = $attr->newInstance();
echo $route->method, ' ', $route->path, PHP_EOL;
}readonly - propiedades en 8.1, clases enteras en 8.2 - garantiza que una propiedad se escribe una sola vez, desde dentro del ámbito de la clase. Es la herramienta para los objetos de valor: cualquier "modificación" se convierte en la construcción de un nuevo objeto, lo que elimina toda una familia de errores de estado compartido. Una clase readonly exige que todas sus propiedades estén tipadas y no sean estáticas.
<?php
declare(strict_types=1);
final readonly class Money
{
public function __construct(
public int $amount,
public string $currency
) {}
public function add(Money $other): self
{
if ($other->currency !== $this->currency) {
throw new InvalidArgumentException(
'Currency mismatch'
);
}
return new self(
$this->amount + $other->amount,
$this->currency
);
}
}
$total = new Money(500, 'EUR');
$total = $total->add(new Money(250, 'EUR'));
echo $total->amount;La sintaxis de callable de primera clase strtoupper(...) (PHP 8.1) crea un Closure a partir de cualquier función o método. A diferencia de las cadenas como 'strtoupper' o los arrays como [$obj, 'method'], el símbolo se verifica en el momento de la creación y las herramientas de análisis estático siguen los tipos de principio a fin. Para un método de instancia, el closure captura el objeto; para un método estático, solo la clase.
<?php
declare(strict_types=1);
$words = ['php', 'rust', 'go'];
$upper = strtoupper(...);
var_dump($upper('php'));
print_r(array_map(strtoupper(...), $words));
final class Mailer
{
public function send(string $to): bool
{
return $to !== '';
}
public static function ping(): string
{
return 'pong';
}
}
$mailer = new Mailer();
$send = $mailer->send(...);
$ping = Mailer::ping(...);
var_dump($send('dev@example.org'), $ping());