Le langage en 2026
Est-ce un langage que vous auriez envie dâĂ©crire pendant quelques annĂ©es ? Aucun benchmark ne rĂ©pond Ă cela ; vous y rĂ©pondez en lisant du code. PHP est aujourdâhui un langage orientĂ© objet, avec ramasse-miettes, aux types dĂ©clarĂ©s et appliquĂ©s Ă lâexĂ©cution aux frontiĂšres des fonctions et des propriĂ©tĂ©s, dotĂ© dâĂ©numĂ©rations, dâimmutabilitĂ©, de closures, dâattributs et dâun gestionnaire de paquets. Un exemple suffit pour le juger, et chaque construction plus jeune que 2022 porte la version qui lâa introduite, pour que vous sachiez ce quâun projet donnĂ© peut utiliser.
Un exemple complet
<?php
declare(strict_types=1);
enum Status: string
{
case Draft = 'draft';
case Published = 'published';
case Archived = 'archived';
public function isVisible(): bool
{
return $this === self::Published;
}
}
final readonly class Article
{
public function __construct(
public string $title,
public Status $status,
public ?\DateTimeImmutable $publishedAt = null,
) {
}
public function publish(\DateTimeImmutable $at): static
{
return new static($this->title, Status::Published, $at);
}
}
function summary(Article ...$articles): string
{
$visible = array_filter($articles, fn (Article $a): bool => $a->status->isVisible());
$titles = array_map(fn (Article $a): string => $a->title, $visible);
return match (count($titles)) {
0 => 'nothing published',
1 => $titles[array_key_first($titles)],
default => implode(', ', $titles),
};
}
$draft = new Article(title: 'Benchmarks', status: Status::Draft);
$live = $draft->publish(new \DateTimeImmutable('2026-09-01'));
echo summary($draft, $live), PHP_EOL; // Benchmarks
echo $draft->status->value, PHP_EOL; // draft
Lisez-le comme vous liriez une pull request. declare(strict_types=1) passe le fichier en typage strict : une chaĂźne passĂ©e lĂ oĂč un int est dĂ©clarĂ© lĂšve une TypeError au lieu dâĂȘtre convertie. Lâinterrupteur agit fichier par fichier, et un projet lâactive dans chacun. Lâenum est une vraie Ă©numĂ©ration, un ensemble fermĂ© dâobjets singletons qui peuvent porter des mĂ©thodes et une valeur sous-jacente. La classe readonly (PHP 8.2) rend chaque propriĂ©tĂ© immuable aprĂšs construction, et son constructeur dĂ©clare et affecte ces propriĂ©tĂ©s en un seul endroit.
Le reste de la syntaxe, vous le connaissez dĂ©jĂ par dâautres langages : les arguments nommĂ©s Ă lâappel, ? pour les types nullables, static comme type de retour, fn pour les closures dâune ligne, et un match qui compare strictement, ne tombe jamais dans la branche suivante et lĂšve une exception quand aucune branche ne correspond.
Ce que lâexemple ne montre pas compte tout autant. Chaque frontiĂšre y est typĂ©e, alors que le langage ne lâexige pas ; la lecture dâune variable non dĂ©finie est un avertissement en PHP 8, et les analyseurs statiques en font une erreur. Le signe $ sur les variables et la flĂšche -> pour lâaccĂšs aux membres sont les deux morceaux de syntaxe qui paraissent Ă©trangers Ă tout le monde, et au bout dâune heure on ne les voit plus.
Les ajouts récents
Les deux derniĂšres versions ont changĂ© la façon dâĂ©crire le code, et un projet sur PHP 8.4 ou plus rĂ©cent utilisera ce quâelles ont apportĂ©.
// PHP 8.4
final class Money
{
public function __construct(
public private(set) int $cents,
public string $currency,
) {
}
public string $formatted {
get => number_format($this->cents / 100, 2) . ' ' . $this->currency;
}
}
$price = new Money(1999, 'EUR');
echo $price->formatted, PHP_EOL; // 19.99 EUR
$price->cents = 5; // Error: Cannot modify private(set) property Money::$cents
public private(set) est la visibilitĂ© asymĂ©trique (PHP 8.4) : la propriĂ©tĂ© se lit de partout et ne sâĂ©crit que depuis lâintĂ©rieur de la classe, ce qui supprime la plupart des getters quâun dĂ©veloppeur Java ou C# sâattend Ă Ă©crire. Le bloc get sous $formatted est un hook de propriĂ©tĂ© (PHP 8.4), une logique attachĂ©e Ă une propriĂ©tĂ© sans changer ses sites dâappel, et cela supprime la plupart des autres. LâopĂ©rateur pipe (PHP 8.5) enchaĂźne les fonctions de gauche Ă droite :
// PHP 8.5
$slug = ' Hello, World '
|> trim(...)
|> strtolower(...)
|> (fn (string $s): string => preg_replace('/[^a-z0-9]+/', '-', $s))
|> (fn (string $s): string => trim($s, '-'));
echo $slug, PHP_EOL; // hello-world
La forme trim(...) est un callable de premiĂšre classe (PHP 8.1), une rĂ©fĂ©rence Ă une fonction sous forme de valeur, avec lâaritĂ© vĂ©rifiĂ©e par le moteur. Ajoutez les attributs, des mĂ©tadonnĂ©es structurĂ©es lues par rĂ©flexion et utilisĂ©es par tous les frameworks pour le routage, la validation et le mapping, et vous avez les constructions que vous croiserez le plus dans une base de code dĂ©marrĂ©e aprĂšs 2024.
Ce que le systĂšme de types fait et ne fait pas
Les types sont dĂ©clarĂ©s sur les paramĂštres, les valeurs de retour, les propriĂ©tĂ©s et les constantes de classe, et le moteur les applique Ă lâexĂ©cution. Les types union (int|string), les types intersection (Countable&Traversable), les types nullables, never, mixed et les Ă©numĂ©rations font tous partie du langage. Une erreur de type est une exception, pas un avertissement, et en mode strict il nây a aucune coercition implicite entre scalaires, Ă lâexception de lâĂ©largissement dâun int en float.
Ce qui manque, autant lâentendre de moi maintenant que le dĂ©couvrir la troisiĂšme semaine. Il nây a pas de gĂ©nĂ©riques dans le langage. Un list<Order> ne peut pas sâexprimer dans une signature ; le paramĂštre est array. LâĂ©cosystĂšme rĂ©pond par une syntaxe de docblock que les deux analyseurs statiques, PHPStan et Psalm, comprennent et appliquent. @param list<Order> $orders est vĂ©rifiĂ© au moment de lâanalyse, dans lâĂ©diteur et en intĂ©gration continue, avec les types template, les types conditionnels et les types de forme, plus riches que tout ce que le langage lui-mĂȘme sait exprimer ; un projet PHP typĂ© exĂ©cute donc un analyseur Ă son niveau le plus strict Ă chaque build et traite sa sortie comme des erreurs de compilation. Que ce soit un substitut acceptable Ă des gĂ©nĂ©riques dans le langage, câest Ă vous dâen juger, selon vos propres habitudes. Câest un substitut, et vous devriez le peser comme tel.
Lâautre absence vient du runtime plutĂŽt que du langage : pas de threads en userland ni de boucle dâĂ©vĂ©nements intĂ©grĂ©e. Concurrence dĂ©crit ce qui existe Ă la place.
La bibliothĂšque standard
strpos voisine avec str_replace, array_key_exists avec in_array, et certaines fonctions prennent lâaiguille en premier quand dâautres prennent la botte de foin. Cette incohĂ©rence est historique, elle est rĂ©elle, et elle ne disparaĂźtra pas. Depuis PHP 8.0, les nouvelles fonctions suivent un seul schĂ©ma de nommage (str_contains, array_is_list, array_find), les fonctions internes lĂšvent TypeError et ValueError sur une entrĂ©e invalide au lieu de retourner false, et tout Ă©diteur dotĂ© dâun serveur de langage PHP complĂšte les noms ; câest ainsi quâune Ă©quipe vit avec le reste. La seconde verrue, ce sont les chaĂźnes, qui sont des suites dâoctets : strlen('Ă©') vaut 2, et le traitement du texte passe par la famille mb_. VoilĂ les deux que vous rencontrerez en premier.
La limite : le systĂšme de types de PHP est appliquĂ© Ă lâexĂ©cution et aux frontiĂšres des fonctions et des propriĂ©tĂ©s, pas Ă lâintĂ©rieur des expressions ni Ă travers les conteneurs gĂ©nĂ©riques. Une Ă©quipe qui veut des garanties Ă la compilation sur ses collections les obtient dâun analyseur statique, pas du langage.
Ce que vous pouvez vĂ©rifier vous-mĂȘme
Installez PHP 8.5 (votre gestionnaire de paquets, ou lâimage Docker officielle php:8.5-cli), collez le premier exemple dans un fichier et lancez-le avec php file.php. Puis cassez-le : passez une chaĂźne lĂ oĂč lâĂ©numĂ©ration Status est attendue, retirez strict_types, Ă©crivez un match sur lâĂ©numĂ©ration en oubliant un cas, puis appelez-le avec ce cas. Les messages dâerreur sont la partie dâun langage avec laquelle on vit, et cinq minutes suffisent pour les juger. Pour le systĂšme de types, lancez PHPStan ou Psalm Ă son niveau le plus strict sur nâimporte quel projet PHP open source dâune taille qui vous parle, et lisez les vingt premiers rĂ©sultats ; ils vous montrent ce que lâanalyseur attrape et que le moteur laisse passer.