Ouvre le fichier qui lance vraiment ta suite de tests en CI. Pas le README, la commande réelle. Quelque part là-dedans, souvent écrite il y a des années par quelqu'un qui a depuis changé de boîte, se cache une décision : les notices de dépréciation, c'est du bruit. Peut-être un flag error_reporting, peut-être un listener qui les avale, peut-être juste un flux de logs que personne ne regarde. Le build passe au vert. Et il reste vert pendant des années, jusqu'à la version majeure où chacune de ces notices silencieuses devient une erreur fatale, et où ce build vert se révèle avoir été un menteur très poli.

terminal
php -d error_reporting=E_ALL -d display_errors=1 vendor/bin/phpunit

Donc voilà ma position sur PHP 8.6, prévu pour le 19 novembre 2026 : ce n'est pas la release qui est intéressante, c'est l'autorisation qu'elle te donne. Pas de réécriture du moteur cette fois, pas d'avalanche d'erreurs de type, pas de mur comme au passage à 8.0. Une application bien testée en 8.4 ou 8.5 migrera en une après-midi. Prends les jours que tu avais réservés pour cette montée de version et consacre-les plutôt à faire sortir ta pipeline en code non nul dès que ton propre code déclenche une dépréciation. Pas un rapport. Pas un tableau de bord. Un build rouge.

Ce n'est pas une version triste pour autant, soyons clairs. L'application partielle de fonctions est passée à 33 contre 0, une marge qu'on ne voit pratiquement jamais sur de la syntaxe, et elle rend rétroactivement l'opérateur pipe de 8.5 vraiment utilisable, parce que tu peux glisser un appel de fonction à trou dans un pipeline au lieu d'emballer tout ça dans une fonction fléchée. Time\Duration est passé à 35 contre 1 et donne enfin un type sans ambiguïté à un timeout, ce que toute personne ayant déjà passé des millisecondes à quelque chose qui attendait des secondes saura apprécier. Il y a aussi clamp(), une énumération SortDirection, des propriétés readonly avec valeurs par défaut, #[\Override] sur les constantes de classe, et json_decode() qui t'indique enfin où le parsing a réellement cassé. Un piège à bien intégrer avec les partiels : les arguments que tu fournis sont évalués au moment où tu construis la closure, pas au moment où tu l'appelles, donc un partiel qui fige une valeur lue depuis un conteneur à portée de requête la capture une fois et la garde. Il y a également de la nouvelle plomberie bas niveau de polling d'I/O dans le cœur, ce qui compte énormément pour les gens qui maintiennent des boucles d'événements et pas du tout pour cette chronique.

L'essentiel de 8.6, c'est une trentaine de dépréciations, et la plupart relèvent du travail qu'une machine fait à ta place. is_double(), is_integer(), doubleval() et spl_object_hash() ont des remplaçants évidents. Passer un objet là où un tableau est attendu s'en va progressivement dans array_walk(), mb_convert_variables(), http_build_query() et les paramètres de filtre zlib et bzip2, et le vote sur array_walk() est passé à 41 contre 3 : personne ne se bat pour ce comportement. readonly cesse d'être utilisable comme nom de fonction, 39 contre 1. is et let deviennent du vocabulaire réservé, façon pour le moteur de garder de la place pour le pattern matching plus tard. Retourner une valeur depuis un bloc finally est déprécié avec le vote le plus net de toute la RFC, 39 contre 3. Le set PHP_86 de Rector plus une lecture attentive du diff couvriront la quasi-totalité. C'est précisément pour ça que le build qui plante est abordable maintenant : la liste est longue mais chaque correction est bon marché, donc tu peux atteindre zéro puis défendre ce zéro.

L'objection honnête, et je me la suis faite moi-même : tu ne contrôles pas le code des dépendances. Transforme les dépréciations en échecs de build et la prochaine version mineure d'un framework ou d'une bibliothèque cliente peut te peindre la pipeline en rouge pour un truc que tu ne peux pas corriger, et la pression devient alors de figer les versions, ce qui est exactement la manière de se retrouver bloqué. C'est juste. La réponse, c'est qu'il s'agit de deux populations différentes qui méritent deux politiques différentes. Ton propre namespace : tolérance zéro, échec immédiat. Tout ce qui est sous vendor : une baseline chiffrée sur laquelle tu as le droit d'être au-dessus de zéro, avec le nombre visible et en baisse, et un ticket ouvert en amont quand il ne bouge plus. Le bridge PHPUnit de Symfony fait des seuils par paquet depuis des années, et tu peux approcher la même chose avec un gestionnaire d'erreurs de vingt lignes. Ce qu'il ne faut surtout pas faire, c'est traiter les deux populations comme du bruit parce que l'une des deux est gênante.

Un point de la liste n'est pas un simple rechercher-remplacer, et il mérite du temps planifié plutôt qu'un vendredi après-midi. Oniguruma, le moteur d'expressions régulières derrière la famille mb_ereg de mbstring, n'est plus maintenu en amont depuis le 24 avril 2025. La réponse de PHP : dépréciation en 8.6 et suppression en 9.0. Faire passer mb_ereg(), mb_ereg_replace() et mb_regex_encoding() vers preg_* avec le modificateur u change la sémantique, pas seulement l'orthographe : les deux moteurs divergent sur assez de détails pour qu'un motif continue de matcher tout en matchant un peu plus, ou un peu moins, qu'avant. Ce mode de défaillance ne lève rien du tout. Il laisse simplement passer une chaîne à travers ton validateur. Écris un test par motif avant d'y toucher, et si tu ne sais pas énumérer tes motifs, cette énumération est le premier ticket. Drupal a déjà une issue ouverte là-dessus, ce qui te dit que l'usage n'a rien d'exotique.

Et puis il y a la catégorie qu'aucun compteur de dépréciations ne fera jamais remonter, parce que rien n'y est déprécié. Les valeurs par défaut INI de session se durcissent en 8.6 : session.use_strict_mode passe à 1, session.cookie_httponly passe à 1, session.cookie_samesite devient « Lax ». Trois bons changements, et chacun peut casser un flux POST cross-site ou un bout de JavaScript qui lit le cookie de session depuis 2017. Mets ces trois valeurs dans ton propre fichier ini avec les réglages que tu as choisis, comme ça un changement apparaît dans une pull request et pas dans un canal de support. Dans le même esprit : trim() et ses voisines suppriment désormais le saut de page par défaut, ce qui compte si tu découpes du texte à largeur fixe ; preg_grep() renvoie false en cas d'erreur au lieu d'un tableau partiel ; tout un lot de fonctions qui émettaient un warning lèvent maintenant une exception. Ta suite de tests peut très bien traverser tout ça sans broncher. UPGRADING reste un fichier qu'on lit avec un café, pas un fichier qu'on grep.

Ce dont je veux vraiment discuter, c'est la politique, pas la release. La toolchain de Go refuse de compiler un import inutilisé, et les développeurs Go ont arrêté de râler environ dix minutes après s'y être habitués. PHP te tend le même levier, il le laisse simplement en position off par défaut et compte sur toi pour être l'adulte de la pièce. Alors : est-ce que ton build échoue aujourd'hui sur une dépréciation levée dans ton propre répertoire src ? Si non, qu'est-ce qui t'en a empêché, un arbre vendor que tu ne peux pas réparer ou une baseline que personne n'a voulu porter ? Et pour les équipes qui font tourner 400 paquets dans composer.json, je veux sincèrement savoir comment vous gardez le compteur vendor honnête sans geler vos dépendances. Réponses en commentaire, surtout celles qui n'ont pas marché.

Il existe une version de cette migration où tu bumps la contrainte, tu regardes la suite passer au vert, et tu livres. Ça marchera. Ça voudra aussi dire que chaque notice que tu n'auras pas lue en novembre reviendra en erreur fatale le jour où tu voudras passer à 9.0, en bloc, sous pression, probablement pendant un gel des releases. Le moment pas cher, c'est la version ennuyeuse. Celle-ci est la version ennuyeuse.