PHP 8.6 en détail : application partielle, Time\Duration et 30 dépréciations avant PHP 9.0
PHP 8.6 sortira le 19 novembre 2026 avec l'application partielle de fonctions, la classe Time\Duration et une trentaine de dépréciations qui tracent la route vers PHP 9.0.
Un article de synthèse détaille les nouveautés, la migration et l'intérêt d'activer E_DEPRECATED dès maintenant.
PHP 8.6 est prévu pour le 19 novembre 2026. L'ensemble des fonctionnalités est figé depuis la beta 3, et la version est plus modeste que la 8.5 : pas de JIT, pas de property hooks, pas de nouveau système de types. Une synthèse de l'auteur drapic88 soutient que la mise à jour prend un après-midi, mais que le vrai travail réside dans les dépréciations, car la plupart deviendront des erreurs fatales dans PHP 9.0.
La fonctionnalité phare est l'application partielle de fonctions, adoptée par 33 voix contre 0. Appeler une fonction avec des espaces réservés renvoie une closure qui attend le reste : ? représente exactement un argument, ... collecte tous les paramètres restants, et les espaces réservés nommés permettent de réordonner la signature de la closure résultante. Cela complète l'opérateur pipe de PHP 8.5 : au lieu d'envelopper chaque étape dans fn($s) => ..., un pipeline s'écrit trim(?) |> str_replace(' ', '-', ?) |> mb_substr(?, 0, 50). Deux pièges à connaître. Les arguments d'un partiel sont évalués à la création, pas à l'appel, contrairement aux fonctions fléchées. Et la PFA ne fonctionne ni avec new, ni avec __get/__set, ni avec les fonctions lisant le scope appelant comme compact(), extract() et func_get_arg(). Un ? positionnel après un argument nommé est une erreur fatale. Contrairement aux first-class callables de la 8.1, un partiel ne conserve que les attributs #[\NoDiscard] et #[\SensitiveParameter].
La deuxième nouveauté est final readonly class Time\Duration, adoptée par 35 voix contre 1. Elle corrige vingt ans d'unités de timeout incohérentes entre sleep(), usleep(), stream_set_timeout(), cURL et les clients Redis. Les fabriques couvrent fromNanoseconds(), fromMicroseconds(), fromMilliseconds(), fromSeconds(int $s, int $ns = 0), fromMinutes(), fromHours() et fromIso8601DurationString(). Les instances proposent add(), sub(), multiplyBy(int), divideBy(int), negate(), absolute(), un compare() statique et les propriétés readonly $seconds, $nanoseconds et $negative. Ni mois ni années, volontairement ; l'arithmétique calendaire reste l'affaire de DateInterval. Premier consommateur dans le cœur : IO\Poll\Context::wait(?Time\Duration $timeout). La liste des fonctions existantes acceptant Duration dans la version finale était encore mouvante pendant le développement.
PHP 8.6 embarque aussi une API bas niveau de multiplexage d'E/S : IO\Poll\Context, StreamPollHandle et Event::Read. Le code applicatif l'utilisera rarement, mais ReactPHP, Amp, Swoole et les runtimes façon FrankenPHP réimplémentent chacun le polling aujourd'hui. Une primitive dans le cœur pourrait unifier le PHP asynchrone à terme.
Parmi les ajouts mineurs : clamp(), qui fonctionne aussi sur les chaînes ; des valeurs par défaut pour les propriétés readonly ; #[\Override] pour les constantes de classe ; un enum SortDirection intégré avec Ascending et Descending ; __debugInfo() sur les enums ; les commentaires de documentation sur les paramètres ; des messages d'erreur affichant les arguments et la position d'échec de json_decode() ; Reflection isReadable() et isWriteable() sensibles au scope ; des optimisations de closures ; les modificateurs d'endianness < et > dans pack()/unpack() pour les flottants et entiers signés ; ainsi que grapheme_strrev(), mysqli_quote_string(), Locale::getDisplayKeyword(), IntlNumberRangeFormatter, la reprise de session TLS et les early data 0-RTT pour les streams, Pdo\Pgsql::ATTR_CHUNK_SIZE, gmp_powm_sec(), gmp_prevprime(), finfo_file() sur les flux distants et des classes de construction d'URI.
Les refus sont tout aussi parlants : l'assignation par pipe (|>=), la construction let (bien que let soit désormais réservé), les generics à bornes effacées, __exists(), plusieurs fonctions utilitaires de tableaux et la réassignation des propriétés readonly promues ont été déclinés. La dépréciation de list() a fait match nul 23 à 23. L'autoloading de fonctions et BackedEnum::values() ont manqué le train ; ReflectionAttribute::getCurrent() était encore en vote à la beta.
Pour la migration, l'article recommande un ordre précis. D'abord, lancer la suite de tests avec error_reporting=E_ALL et display_errors=1, car un CI qui masque E_DEPRECATED cache la charge réelle. Ensuite, les renommages mécaniques : is_double() vers is_float(), is_integer()/is_long() vers is_int(), doubleval() vers floatval(), spl_object_hash() vers spl_object_id(), spl_classes() vers la réflexion, strcoll() et SORT_LOCALE_STRING vers le Collator intl, metaphone() vers soundex(), et abandon du drapeau $case_insensitive de define(). Troisièmement, ne plus passer d'objets à array_walk(), array_walk_recursive(), deflate_init(), inflate_init(), mb_convert_variables() et http_build_query() ; la correction est généralement get_object_vars($obj). Sont aussi dépréciés : is_a() et is_subclass_of() avec une chaîne en premier argument, les méthodes CSV de SplFileObject et les implémentations incomplètes de session_set_save_handler(). Quatrièmement, libérer les identifiants : readonly comme nom de fonction, is et let comme identifiants, namespace comme constante de classe et _ comme constante sont dépréciés, is étant réservé pour le futur pattern matching. Cinquièmement, la famille mb_ereg* est dépréciée en 8.6 et supprimée en 9.0 car Oniguruma n'est plus maintenu en amont depuis le 24 avril 2025 ; migrer vers preg_* avec le modificateur u demande un vrai budget, comme le montre un ticket Drupal ouvert. Sixièmement, les changements silencieux : les valeurs INI de session deviennent session.use_strict_mode=1, session.cookie_httponly=1 et session.cookie_samesite="Lax" ; retourner une valeur depuis __construct(), __destruct() ou finally est déprécié ; trim() et ses cousins retirent désormais le form feed ; de nombreuses fonctions lèvent des exceptions au lieu d'avertir ; preg_grep() renvoie false en cas d'erreur ; ?? et empty() ne déclenchent plus __get() après matérialisation par __isset().
L'auteur estime qu'une application Laravel ou Symfony bien testée sur 8.4+ demande une demi-journée à une journée, principalement à attendre les dépendances, avec le set PHP_86 de Rector, PHPStan ou Psalm et composer why pour l'essentiel du travail. Un code legacy utilisant mb_ereg relève d'une autre conversation.
Tribune des lecteurs
Pas encore de contributions — lance le débat.
Lance la discussion
Pas de compte ni de mot de passe — saisis simplement ton adresse e-mail et nous t’envoyons un lien de connexion à usage unique. Première visite ? Tout se met en place automatiquement.
Ton évaluation sera appliquée automatiquement après ta connexion.
Vérifie ta boîte mail
Nous avons envoyé un lien de connexion à …. Ouvre-le sur cet appareil — cet onglet te connectera automatiquement.
Rien reçu ? Vérifiez le dossier spam — et marquez le message « Non spam » pour qu'il arrive directement la prochaine fois.