PHP 8.6 est entrée dans sa dernière ligne droite : alpha 1 sortie le 2 juillet, feature freeze le 11 août, GA visée le 19 novembre 2026. Écrit en mai à mi-parcours, ce billet a été remis à jour en juillet : la plupart des votes sont tranchés, la colonne « implémenté » s'est allongée. Et un sujet à part : les generics, dont la nouvelle proposition a finalement été déclinée.
Et pour les versions précédentes...
PHP 8.2 : la consolidation du système de types et modernisation de l’API
Et pour les versions précédentes...
PHP 8.4 : rattrapage de retard ou véritable modernisation ?
Et pour les versions précédentes...
PHP 8.1 : l’expression après la révolution
Sept RFCs déjà implémentées
Liste vérifiée sur php.watch/versions/8.6/rfcs. Trois méritent qu'on s'y attarde, les quatre autres tiennent en une ligne.
clamp v2
Signature finale (RFC clamp_v2) :
clamp(mixed $value, mixed $min, mixed $max): mixedBorne une valeur entre un min et un max. Accepte tout type comparable : entiers, floats, chaînes (tri alphabétique), DateTimeInterface. Remplace max(min($v, $max), $min), motif qu'on retrouve dans la pagination, les dimensions d'image, la normalisation de score. Auteur : kylekatarnls.
Debugable Enums
Le RFC original sur les enums interdisait toutes les méthodes magiques sauf __call, __callStatic, __invoke. Conséquence : impossible d'utiliser __debugInfo() pour customiser la sortie var_dump. Cette restriction saute en 8.6. Désormais une enum peut renvoyer ce qu'elle veut quand on l'inspecte :
enum(Foo::Bar) (1) {
[0]=> string(14) "Foo::Bar = Baz"
}enum SortDirection
Une enum native, unbacked (RFC sort_direction_enum) :
enum SortDirection
{
case Ascending;
case Descending;
}Le choix « unbacked » est explicite : pas de valeur scalaire associée, à chaque application de mapper vers SORT_ASC, 'asc', ou autre selon son besoin.
Les quatre autres
isReadable/isWritablereflection methods : checks natifs sur l'accessibilité d'une propriété, utile pour les hydraters et serializers.mysqli_quote_string: quote sans connexion ouverte.- Add Form Feed in
trimFunctions : le caractère\f(form feed,0x0C) rejoint la liste par défaut des caractères supprimés partrim. - Oniguruma maintenance end and end of mbregex : le backend Oniguruma est retiré de
ext/mbstring. Les fonctionsmb_ereg*continuent à fonctionner avec un backend interne PCRE2.
Cinq RFCs acceptées
Implémentation en cours. Liste complète, focus sur les deux qui changent la façon de coder.
Closure optimizations
Deux gains (RFC closure-optimizations) :
- Inférence automatique en
static: une closure qui ne peut pas utiliser$this(pas de référence directe, pas de méthode statique appelée, pas de closure imbriquée non-statique, pas derequire/include/eval) est convertie enstaticautomatiquement. - Cache des closures stateless : une closure qui ne capture rien et ne déclare aucune variable statique est réutilisée d'un appel à l'autre, la même instance en mémoire. Gain mesuré sur benchmarks synthétiques : « about 80% » selon le RFC.
Effet de bord à retenir : ReflectionFunction::getClosureThis() retourne désormais null pour les closures inférées statiques. Et deux closures stateless lexicalement identiques deviennent le même objet, donc $a === $b peut passer de false à true après upgrade.
Partial Function Application (v2)
Syntaxe finalisée (RFC partial_function_application_v2) :
?: remplace un argument à cette position...: représente zéro ou plusieurs arguments restants
$shorten = str_word_count(...);
// $shorten(string $string, int $format = 0, string $characters = "")
$mid = foo(1, ?, 3, ?);
// $mid attend les arguments 2 et 4Vote : 33 pour, 0 contre, 0 abstention. La v1 de 2021 avait été retoquée sur des cas limites du moteur d'inférence ; cette v2 réduit la surface et passe.
Les trois autres
- Add
pack()/unpack()endianness modifiers for floating-point numbers : les float rejoignent les modifiers</>qui existaient déjà pour les entiers. - Add
pack()/unpack()support for endianness modifiers on integers : extension symétrique côté entiers. - Exempt input type and value validation from BC Break policy : décision méta, les fixes de validation d'entrée ne déclenchent plus la procédure de BC break complète. Pertinent pour les contributeurs, transparent pour les utilisateurs.
Ce qui était en vote, ce qui reste en discussion
À surveiller pour la fin du semestre, sans garantie de vote favorable.
Depuis le pointage de mai, les urnes ont parlé : #[\Override] for class constants est implémentée, Stream Error Handling Improvements est acceptée. Le sort des cinq autres (PFA optional parameters, ext/uri, secure session defaults, function arguments in errors, SNMP) se suit sur php.watch. Au 18 juillet, « Add ReflectionAttribute::getCurrent() » est en vote actif.
En discussion (toujours, au 18 juillet) : True Async · BackedEnum::values(). La Polling API, elle, a depuis été acceptée.
True Async mérite un mot : c'est la proposition la plus ambitieuse non encore tranchée (RFC true_async). Trois primitives : spawn(), await(), suspend(). Le système englobe les Fibers existants, ajoute un Scheduler et un Reactor (boucle événementielle), un mécanisme de cancellation coopératif, et une détection de deadlock natif. Si ça passe, c'est plus structurant que toutes les autres réunies. Mais le statut Draft suggère qu'on en reparlera surtout pour PHP 9.0.
Le sujet à part : Bound-Erased Generic Types
C'était le retour des generics. Mise à jour de juillet : la proposition a été déclinée, les generics n'entreront pas dans PHP 8.6. L'analyse qui suit reste utile pour comprendre ce qui a été tenté, et pourquoi le débat reviendra. Trois caractéristiques rendaient cette proposition différente des tentatives précédentes.
Scope large
Selon le texte du RFC : « Generic type parameters can be declared on classes, interfaces, traits, functions, methods, closures, and arrow functions. »
Donc tout. Pas un compromis « interfaces seulement » comme certaines propositions intermédiaires.
Type erasure plutôt qu'enforcement runtime
« Type parameters erase to their bound at runtime; the pre-erasure form is preserved for Reflection and consumed by static analyzers. »
Les bornes (<T extends Stringable>) sont vérifiées par les outils d'analyse statique (PHPStan, Psalm). À l'exécution, le moteur traite T comme sa borne, ici Stringable. Aucun coût runtime ajouté. La métadonnée pre-erasure est conservée pour la Reflection, ce qui permet aux IDE et aux libs comme Doctrine de la lire si besoin.
Static analysis plutôt que compile-time enforcement
C'est le compromis explicite : le RFC « emphasizes static analysis rather than compile-time enforcement ». Concrètement, un code générique faux ne sera pas refusé par le parseur PHP, il sera juste détecté comme erreur par PHPStan. La voix de Gmati dans le débat : PHP utilise déjà des generics par docblock (@template) sans enforcement runtime ; cette RFC ne fait que formaliser la pratique dans la syntaxe native.
Ce que ça donnerait
interface Collection<T>
{
public function add(T $item): void;
public function first(): ?T;
}
final class PostCollection implements Collection<Post>
{
public function add(Post $item): void { /* T = Post */ }
public function first(): ?Post { /* T = Post */ }
}À l'exécution, T est traité comme mixed (ou la borne déclarée). Mais PHPStan, Psalm, PHPStorm verront Post et inféreront en conséquence.
Calendrier
Fin de l'histoire pour ce cycle : la RFC a été déclinée, avant même la fin de l'été qu'exigeait le calendrier. Le point de friction sur internals est resté celui du départ : des generics non-enforced créent-ils plus de confusion qu'ils n'en évitent ? Le sujet, ouvert depuis 2016, attendra une prochaine tentative.
La suite
PHP 8.6 ressemble à une release d'incrément propre : des utilities qui rangent du legacy, des optimisations gratuites, et un rendez-vous manqué avec les generics. Prochaines étapes : la beta du 13 août, puis la GA du 19 novembre.
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).