Le support de sécurité de PHP 8.1 s'est arrêté le 31 décembre 2025. La version est morte, et 205 propriétés de ce blog reposent sur un mot-clé qu'elle a livré. Ce que 2021 a présenté comme du raffinement porte aujourd'hui la charpente.
Une version morte dont on écrit encore le code
Le support de sécurité de PHP 8.1 a pris fin le 31 décembre 2025. Plus aucun correctif, y compris de sécurité. Sur le papier, il n'y a plus rien à en dire.
Ce blog tourne en PHP 8.5. Et dans son src/, 205 propriétés portent un mot-clé arrivé en novembre 2021 avec 8.1, sans lequel la moitié de son architecture ne tiendrait pas.
C'est le sujet. Pas une visite du changelog, mais un relevé : quatre ans après, qu'est-ce que 8.1 a réellement laissé dans un projet qui tourne ? Le comptage est fait à la commande sur le dépôt, fonctionnalité par fonctionnalité, et il réserve au moins une surprise.
Après le fracas de PHP 8.0, 8.1 est passée pour la version tranquille. Le relevé dit autre chose.
readonly : 205 propriétés, et un garde-fou qu'on n'avait pas demandé
L'immutabilité en PHP, avant 8.1, se plaidait plus qu'elle ne s'écrivait. On mettait la propriété en privé, on écrivait un accesseur, on n'écrivait pas de mutateur, et on espérait que personne ne contourne.
readonly a réglé la question en un mot : une propriété assignable une seule fois, dans le constructeur, refus fatal ensuite. Combinée à la promotion de constructeur de 8.0, la déclaration tient en quatre lignes.
Ce que 2021 n'annonçait pas, c'est ce que ce mot-clé deviendrait sur un serveur qui ne meurt pas. Ce blog tourne en FrankenPHP worker mode : le kernel démarre une fois et sert des milliers de requêtes, donc un service partagé garde entre deux visiteurs tout ce qu'il a écrit dans ses propriétés. Une classe readonly ne peut rien écrire du tout. Elle est sûre par construction, sans revue et sans discipline.
Le compte, sur ce dépôt : 205 propriétés readonly, la forme exacte livrée par 8.1. Et 408 classes entièrement readonly, dont 405 en final readonly class : cette forme-là est arrivée un an plus tard avec PHP 8.2, qui a promu le mot-clé de la propriété à la classe. 8.1 a posé le mot, 8.2 lui a donné sa portée. Les rares exceptions se justifient une par une, et elles portent toutes un reset(). J'ai raconté ce basculement dans Les design patterns supposent que le process va mourir.
Un mot-clé de confort est devenu un garde-fou d'exécution. Personne ne l'avait écrit dans la RFC.
Les enums : la fonctionnalité qui s'est diffusée le plus vite
Avant, un ensemble de valeurs fermé s'écrivait en constantes de classe, et la signature acceptait une string. Rien n'empêchait de passer 'banana', et on l'apprenait en production.
Les enums de 8.1 ne sont pas des constantes déguisées. Ce sont des objets : ils portent des méthodes, implémentent des interfaces, et le match les rend exhaustifs. Celui qui classe les références de ces billets tient en quinze lignes :
// src/Enum/Content/PostReferenceType.php
enum PostReferenceType: string
{
case SOURCE = 'source';
case FURTHER_READING = 'further_reading';
public function getLabel(): string
{
return match ($this) {
self::SOURCE => 'Source',
self::FURTHER_READING => 'Pour aller plus loin',
};
}
}Le libellé vit avec la valeur au lieu de traîner dans un tableau de configuration ailleurs. Ajouter un cas sans compléter le match fait échouer l'analyse statique avant même l'exécution.
Le compte : 30 enums dans src/Enum/, dont 18 portent un match. C'est la fonctionnalité de 8.1 la plus répandue en nombre de fichiers, et celle dont on ne parle plus parce qu'elle est devenue le réflexe.
Les callables de première classe : une syntaxe qui attendait son opérateur
Référencer une méthode, avant 8.1, se faisait par chaîne ou par tableau : [$objet, 'methode']. Aucune vérification, aucune autocomplétion, et une faute de frappe qui ne se voyait qu'à l'exécution.
La syntaxe (...) a rendu la référence typée et navigable. Trente-cinq usages ici. Le plus intéressant est celui-ci, parce qu'il montre à quoi la fonctionnalité servait vraiment sans qu'on le sache encore en 2021 :
// src/Block/Concerns/SanitizesInlineHtmlTrait.php
$clean = $stripped
|> $sanitizer->sanitizeHtml(...)
|> (fn(string $h): string => $this->enrichLinks($sanitizer, $h));L'opérateur |> est arrivé avec PHP 8.5, quatre ans plus tard. Il prend à sa droite quelque chose d'appelable, et la forme la plus lisible de « quelque chose d'appelable » est précisément celle que 8.1 avait introduite. La fonctionnalité de 2021 a trouvé son usage confortable en 2025.
never : trois usages, et c'est normal
Une fonction qui ne rend jamais la main, parce qu'elle lève ou termine le script, se déclarait void faute de mieux. void dit « rend sans valeur », pas « ne rend jamais ». never dit la seconde chose, et l'analyse statique s'en sert pour savoir que le code après l'appel est mort.
Ce dépôt en compte trois. Le plus parlant est un contrôleur qui n'est jamais exécuté :
// src/Controller/Security/LogoutAction.php
#[Route('/deconnexion', name: self::class, methods: ['GET'])]
public function __invoke(): never
{
throw new \LogicException('This method can remain empty - it will be intercepted by the logout key on your firewall.');
}La route existe pour que Symfony ait une URL de déconnexion, le pare-feu l'intercepte avant le contrôleur, et le corps ne tourne jamais. never encode exactement ça dans la signature.
Trois usages sur un projet de cette taille, c'est peu, et ce n'est pas un échec. never sert aux fonctions terminales, et une application bien découpée en a très peu.
Les Fibers : la fondation qu'on n'écrit jamais soi-même
Les Fibers permettent de suspendre et reprendre une exécution. Elles ont remplacé les acrobaties à base de générateurs et de rappels imbriqués que les boucles d'événements PHP bricolaient depuis des années.
Zéro usage direct ici, et il en va de même dans l'écrasante majorité des applications. C'est une primitive pour ceux qui écrivent des serveurs et des boucles d'événements, pas pour ceux qui écrivent des contrôleurs. Ce qui n'empêche pas ce blog d'en dépendre entièrement, puisqu'il tourne derrière un serveur qui les utilise. J'ai regardé la primitive de près dans Fibers PHP : anatomie d'une primitive mal aimée.
Les types d'intersection : zéro, et c'est un résultat
Reste la fonctionnalité que les articles de sortie mettaient en avant, Loggable&Cacheable, pour exiger d'un argument qu'il honore deux contrats à la fois.
Usages sur ce dépôt : zéro.
Ce n'est pas un oubli. Quand deux contrats vont ensemble assez souvent pour mériter une signature, on écrit l'interface qui étend les deux et on la nomme. Le nom documente l'intention, l'intersection anonyme la répète à chaque signature. Le besoin réel est plus rare que la démonstration ne le laissait croire, et une fonctionnalité peu utilisée n'est pas une fonctionnalité ratée : c'est une fonctionnalité dont le cas d'usage est étroit.
Ce que « raffinement » voulait dire
Le relevé, quatre ans après : 205 propriétés readonly et 408 classes qui le sont entièrement, 30 enums dont 18 exhaustifs, 35 callables de première classe, 3 never, 0 intersection, 0 Fiber écrite à la main sous un serveur qui n'existerait pas sans elles.
Une version se juge mal le jour de sa sortie. En novembre 2021, readonly passait pour du sucre syntaxique qui évitait d'écrire un accesseur. Ce qui l'a rendu structurant, c'est un changement arrivé ailleurs et plus tard : le jour où le process PHP a cessé de mourir entre deux requêtes, l'immutabilité est passée du confort à la sûreté. La fonctionnalité n'a pas bougé. C'est le monde autour qui a changé de forme, et elle s'est trouvée au bon endroit.
C'est probablement la seule chose utile à retenir d'une version morte. Les fonctionnalités qui comptent ne sont pas celles qu'on remarque à la sortie, ce sont celles dont un usage imprévu vient révéler la portée.
Et sur la version que vous installez cette semaine : laquelle de ses nouveautés sera devenue une convention chez vous dans quatre ans ?
Une coquille, une erreur dans ce billet ? Signale-la-moi.
Ce billet est publié sous licence Creative Commons BY-NC-SA 4.0 (attribution, pas d'usage commercial, partage dans les mêmes conditions).