Explorez la mécanique interne des attributs, de readonly, des callables de première classe et du typage strict de PHP 8.
Ouvrir cette leçon dans KodokonLes attributs remplacent les annotations en docblocks par de vraies classes, validées par le moteur. Un attribut n'est qu'une métadonnée dormante : écrire #[Route('/users')] ne crée aucun objet. Il ne prend vie que lorsqu'un code l'interroge par réflexion et appelle newInstance() - c'est exactement ce que font les frameworks au démarrage pour construire leurs tables de routage. La cible autorisée (TARGET_METHOD, TARGET_CLASS...) est elle-même déclarée par un attribut sur la classe d'attribut.
<?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 - propriétés en 8.1, classes entières en 8.2 - garantit qu'une propriété n'est écrite qu'une seule fois, depuis la portée de la classe. C'est l'outil des objets-valeurs : toute « modification » devient la construction d'un nouvel objet, ce qui élimine une famille entière de bugs d'état partagé. Une classe readonly impose que toutes ses propriétés soient typées et non statiques.
<?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 syntaxe de callable de première classe strtoupper(...) (PHP 8.1) crée une Closure à partir de n'importe quelle fonction ou méthode. Contrairement aux chaînes comme 'strtoupper' ou aux tableaux [$obj, 'method'], le symbole est vérifié dès la création et les outils d'analyse statique suivent les types de bout en bout. Pour une méthode d'instance, la closure capture l'objet ; pour une méthode statique, seulement la classe.
<?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());