Aller au contenu principal
Retour au blog

PHP 8.6 arrive le 19 novembre, PHP 8.2 expire le 31 décembre

PHP 8.2 perd ses correctifs de sécurité le 31/12/2026 et PHP 8.6 sort le 19/11. Support, BC breaks testés sur la RC3 et checklist de migration Symfony.

Flavien Métivier · 11 min de lecture
Sommaire · 7 parties

PHP 8.6 sort le 19 novembre, et sa migration est l’une des plus légères depuis des années. Le sujet qui devrait occuper votre agenda est pourtant ailleurs : PHP 8.2 perd son support de sécurité le 31 décembre 2026, une dizaine de semaines après la publication de cet article. Voici ce qu’il faut décider et faire d’ici là, dans le bon ordre.

Les nouveautés de PHP 8.6 ont été présentées dans notre point d’étape de juillet sur les RFC. Cet article-ci parle de migration. Tout le code qui suit a été exécuté sur l’image Docker officielle php:8.6-rc-cli, soit PHP 8.6.0RC3, et comparé à PHP 8.5.11.

Le calendrier : ce qui est ferme, ce qui ne l’est pas

PHP 8.6.0 est en release candidate : la RC3 est publiée sur php.net depuis le 8 octobre 2026, la RC4 est attendue le 22 octobre, la RC5 le 5 novembre, et la disponibilité générale est prévue le 19 novembre 2026 selon le calendrier du wiki PHP. Le feature freeze date du 22 septembre : ce qui est dans la RC3 est, à quelques corrections près, ce que vous aurez en GA. Vous pouvez donc tester dès aujourd’hui sur un périmètre stable. Mais une GA n’est pas une version à mettre en production le jour J — et ce n’est pas elle qui fixe votre échéance.

La vraie urgence : la fin de support de PHP 8.2

La fin de support de PHP 8.2 est fixée au 31 décembre 2026 : après cette date, la branche ne reçoit plus aucun correctif de sécurité. État des branches à mi-octobre 2026, d’après php.net/supported-versions :

  • PHP 8.2 : sortie le 8 décembre 2022, support actif terminé le 31 décembre 2024, sécurité jusqu’au 31 décembre 2026.
  • PHP 8.3 : sortie le 23 novembre 2023, support actif terminé le 31 décembre 2025, sécurité jusqu’au 31 décembre 2027.
  • PHP 8.4 : sortie le 21 novembre 2024, support actif jusqu’au 31 décembre 2026, sécurité jusqu’au 31 décembre 2028.
  • PHP 8.5 : sortie le 20 novembre 2025, support actif jusqu’au 31 décembre 2027, sécurité jusqu’au 31 décembre 2029.
  • PHP 8.6 : GA prévue le 19 novembre 2026.

Concrètement : si vous êtes en 8.2, toute faille du moteur ou d’une extension (intl, xml, curl…) découverte en janvier restera sans correctif officiel, et vos audits de sécurité, votre assureur cyber ou vos clients régulés finiront par le relever. Si vous êtes en 8.4, rien ne casse le 1er janvier, mais seules les failles de sécurité seront encore corrigées : un état acceptable, pas un état cible. En 8.3 ou 8.5, vous avez le temps. Et une application encore en 8.1 n’est déjà plus supportée depuis un an.

Côté Symfony, le plancher PHP dessine le chemin : Symfony 7.4 LTS exige PHP 8.2 minimum, Symfony 8.0 exige PHP 8.4. Une application Symfony 7.4 en PHP 8.2 peut donc passer en 8.4 ou 8.5 sans changer de version majeure du framework : c’est la migration la plus rentable de l’automne. Notre recommandation, à prendre comme un avis d’auteur : viser PHP 8.5 en production d’ici mi-décembre — c’est la branche la plus longue, avec la sécurité jusqu’à fin 2029 — et traiter PHP 8.6 comme une cible du premier trimestre 2027, une fois les premiers patchs sortis et vos dépendances validées. Tester 8.6 maintenant sert à ne pas être surpris, pas à déployer.

Tester PHP 8.6 dès aujourd’hui, sans toucher à la prod

Les images officielles existent : php:8.6-rc-cli (alias de 8.6.0RC3-cli), ainsi que les variantes -fpm, -apache et -alpine. Deux pièges constatés à l’exécution. D’abord, les images RC 8.6 sont compilées en ZTS, là où php:8.5-cli est NTS : ne comparez pas des benchmarks entre les deux. Ensuite, OPcache est intégré au binaire depuis PHP 8.5 : un docker-php-ext-install opcache fait échouer le build (cp: cannot stat 'modules/*'). Retirez-le de vos Dockerfiles avant de passer en matrice.

bash
# syntax=docker/dockerfile:1
ARG PHP_VERSION=8.5
FROM php:${PHP_VERSION}-cli AS test
# Extensions courantes d’une app Symfony ; opcache est intégré depuis 8.5
RUN apt-get update && apt-get install -y --no-install-recommends git unzip libicu-dev libzip-dev \
 && docker-php-ext-install -j"$(nproc)" intl pdo_mysql zip \
 && rm -rf /var/lib/apt/lists/*
COPY --from=composer:2.10 /usr/bin/composer /usr/bin/composer
RUN useradd -m app
USER app
WORKDIR /app

Ce Dockerfile se construit tel quel en 8.5.11 et en 8.6.0RC3, avec intl, pdo_mysql, zip et OPcache chargés. Pour la CI, gardez la cible stable dans le workflow requis, et placez la RC dans un workflow séparé, planifié et non requis par la protection de branche : vous avez l’information sans rendre la CI tolérante aux échecs.

yaml
# .github/workflows/php-next.yml : information sur la prochaine version
name: php-next
on:
  schedule:
    - cron: '0 6 * * 1'
  workflow_dispatch:
jobs:
  tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - run: docker build --build-arg PHP_VERSION=8.6-rc -t app-test:8.6 .
      - run: |
          docker run --rm -v "$PWD:/app" -w /app app-test:8.6 sh -c '
            composer install --no-interaction --prefer-dist &&
            php -d error_reporting=-1 vendor/bin/phpunit --fail-on-deprecation'

L’option --fail-on-deprecation de PHPUnit fait remonter chaque E_DEPRECATED de 8.6 comme un échec : c’est exactement la liste de travail dont vous avez besoin. Si une dépendance plafonne php sous 8.6 dans son composer.json, --ignore-platform-req=php+ débloque l’installation pour le test — jamais au-delà.

Ce que PHP 8.6 casse vraiment

Le changement le plus visible concerne les sessions. Trois valeurs par défaut se durcissent : session.use_strict_mode passe de 0 à 1, session.cookie_httponly de 0 à 1, et session.cookie_samesite de « non défini » à Lax. Vérifié avec php -i : la RC3 affiche On, On et Lax, quand 8.5.11 affiche Off, Off et no value.

Pour une application Symfony, l’impact est faible : FrameworkBundle impose déjà ses propres valeurs (cookie_httponly: true, cookie_samesite: lax, cookie_secure: auto) et active le mode strict. Le risque est ailleurs : tout ce qui appelle session_start() hors du kernel — scripts legacy, endpoints PHP isolés, passerelles vers un vieux monolithe —, le JavaScript qui lit le cookie de session via document.cookie, et les flux POST cross-site (retour de paiement 3-D Secure, SSO, iframe partenaire), qui n’enverront plus le cookie avec Lax. Le bon réflexe est de figer ces valeurs explicitement, quelle que soit la version :

bash
; php.ini : ne plus dépendre des défauts de la version
session.use_strict_mode = 1
session.cookie_httponly = 1
session.cookie_samesite = "Lax"
session.cookie_secure = 1

Migration Symfony en vue ?

Bear Upgrade migre votre codebase 2,5× à 4,5× moins cher qu'une ESN, selon sa maturité.

Estimer ma migration →

Les autres changements de comportement à connaître, tirés du fichier UPGRADING de la branche :

  • preg_grep() retourne désormais false sur erreur PCRE (UTF-8 invalide avec /u), au lieu d’un tableau partiel. Vérifié. Un foreach sans test sur son résultat devient un bug visible.
  • trim(), ltrim(), rtrim() suppriment aussi le form feed par défaut. Un parseur qui s’en sert comme séparateur change de comportement.
  • Les classes Date déclarent un type de retour tentatif static sur add(), sub(), modify(), setTimezone()… Une sous-classe de DateTimeImmutable — typiquement une clock maison — qui les surcharge avec un autre type reçoit une dépréciation. Correctif : déclarer : static.
  • Les octets NUL dans un chemin lèvent une ValueError sur file_exists(), is_file(), filesize(), scandir()… Bonne nouvelle pour la sécurité, mais un @file_exists($input) verra passer des exceptions.
  • Les handlers de session maison : avec lazy_write, une session vide passe par updateTimestamp() plutôt que write(). Si votre handler Redis ou base de données détruisait la session dans write(), déplacez cette logique.
  • Collisions de noms : une fonction globale clamp() ou une classe globale SortDirection entrent désormais en conflit avec le natif. Un grep suffit à les trouver.

Les dépréciations à nettoyer avant PHP 9

Les dépréciations ne cassent rien en production, mais elles polluent les logs et deviendront des erreurs. Voici ce que la RC3 émet réellement, avec un gestionnaire d’erreurs qui les affiche :

bash
[E_DEPRECATED] Returning from a finally block is deprecated
[E_DEPRECATED] Returning a value from a constructor is deprecated
[E_DEPRECATED] Function spl_object_hash() is deprecated since 8.6, consider using spl_object_id() instead
[E_DEPRECATED] Function is_integer() is deprecated since 8.6, use is_int() instead
[E_DEPRECATED] Function is_double() is deprecated since 8.6, use is_float() instead
[E_DEPRECATED] Function doubleval() is deprecated since 8.6, use floatval() instead
[E_DEPRECATED] Function metaphone() is deprecated since 8.6, use a userland phonetic matching library instead
[E_DEPRECATED] array_walk(): Passing an object for argument #1 $array to array_walk() is deprecated, call get_object_vars() first instead
[E_DEPRECATED] Calling a function "readonly" is deprecated since 8.6
[E_DEPRECATED] Using "let" as a class name is deprecated since 8.6
  • return dans un finally : le comportement ne change pas — le return du finally écrase toujours celui du try et avale les exceptions —, mais l’anti-pattern est désormais signalé. Sortez la valeur du bloc.
  • spl_object_hash() est le candidat numéro un au bruit en CI : très répandu dans les dispatchers maison, les identity maps et les caches par objet. spl_object_id() le remplace, et un grep -rn spl_object_hash vendor/ vous dira combien de tickets ouvrir en amont.
  • Les alias historiques is_integer, is_long, is_double, doubleval se remplacent mécaniquement par is_int, is_float, floatval.
  • Les handlers de session qui n’implémentent pas create_sid() et validateId() émettent une dépréciation. Ceux de Symfony les fournissent ; les vôtres, pas forcément.
  • Mbregex (mb_ereg*, mb_split) est déprécié avec l’abandon d’Oniguruma : basculez sur preg_*.

À l’inverse, la dépréciation de list() a été refusée au vote (23 voix contre 23) : rien à faire de ce côté. Pour la liste faisant foi, référez-vous à la section 4 du fichier UPGRADING plutôt qu’aux résumés, et laissez votre CI parler.

Les nouveautés qui valent un ticket

Sans refaire le tour des RFC, voici l’API réelle telle qu’exécutée sur la RC3, et ses limites. L’application partielle de fonction utilise ? pour un argument à fournir plus tard et ... pour tous les suivants ; elle retourne une Closure et remplace bien des fn () => jetables. Elle fonctionne sur les fonctions, les méthodes et les arguments nommés, mais pas sur new : new Foo(?) lève une erreur fatale à la compilation.

php
<?php
declare(strict_types=1);

$slug = str_replace(' ', '-', ?);
echo $slug('hello big world');                       // hello-big-world

print_r(array_map(str_pad(?, 5, '*'), ['a', 'bb'])); // a**** / bb***

// clamp() : bornes inclusives, tout type comparable
$limit = clamp((int) $request->query->get('limit', 20), 1, 100);
// clamp(1, 10, 0) lève ValueError : $min doit être <= $max

// Propriétés readonly avec valeur par défaut
final class RetryPolicy
{
    public readonly int $maxRetries = 3;   // erreur de compilation avant 8.6
}

Deux finesses vérifiées sur clamp() : elle retourne la borne telle quelle, donc clamp(1.5, 0, 1) donne l’entier 1 ; et une valeur NAN est renvoyée sans erreur, alors qu’une borne NAN lève une ValueError. Si vos bornes viennent de la configuration, validez leur ordre en amont.

Time\Duration est une classe final readonly à précision nanoseconde, avec des fabriques (fromMilliseconds(), fromSeconds(), fromMinutes(), fromIso8601DurationString()…), l’arithmétique (add(), multiplyBy()…) et la comparaison native. Elle est idéale pour des timeouts et des stratégies de retry sans l’ambiguïté « millisecondes ou secondes ? ». Mais elle n’est pas interopérable avec DateInterval : DateTimeImmutable::add() la refuse avec une TypeError, et P1D n’est pas accepté, seule la partie horaire d’une durée ISO 8601 l’est. Les composants Symfony continueront d’attendre des entiers pour un moment.

Enfin, l’enum global SortDirection (Ascending, Descending) est un contrat de type pour vos query builders, pas davantage : aucune fonction native de 8.6 ne l’accepte encore. Et l’API Io\Poll, qui remplace stream_select() par epoll ou kqueue, concerne les workers longs, pas une application FPM classique.

Checklist de migration Symfony, semaine par semaine

  • Cette semaine : inventorier la version PHP réelle de chaque environnement — production, staging, CI, images Docker, runners, require.php et config.platform.php de composer.json. Tout ce qui est en 8.2 ou moins passe en priorité, avec échéance au 31 décembre.
  • Fin octobre : passer la CI requise en PHP 8.5, retirer opcache des docker-php-ext-install, lancer la suite avec --fail-on-deprecation. En Symfony 7.4 LTS, aucun changement de framework n’est nécessaire.
  • Début novembre : ajouter le workflow d’information 8.6, chercher les collisions et dépréciations (grep -rn "function clamp(\|SortDirection\|spl_object_hash\|is_integer\|is_double" src vendor), auditer les session_start() hors kernel et les handlers de session maison, figer les directives de session.
  • Avant le 19 novembre : ouvrir les tickets amont pour les dépréciations venant de vendor/, vérifier la disponibilité de vos extensions pour 8.6, planifier la bascule de production en 8.5 avec une fenêtre de retour arrière.
  • Avant mi-décembre : production en PHP 8.5, avant le gel de fin d’année. PHP 8.6 en préproduction au premier trimestre 2027.

Une zone d’ombre, à dire honnêtement : le support de PHP 8.6 par Rector, Doctrine et vos extensions n’était pas confirmé à l’écriture de cet article. Testez-les sur votre projet avant de promettre une date. Pour la mécanique Rector, la procédure décrite pour corriger les nullable implicites lors du passage en 8.4 reste valable. Et si vous partez de plus loin, les incompatibilités de la migration PHP 8.1 vers 8.3 sont les mêmes marches à franchir.

Retenez trois choses. La migration vers 8.6 est légère et peut attendre le premier trimestre 2027. L’échéance du 31 décembre sur PHP 8.2, elle, ne se négocie pas. Et 8.5 est la cible par défaut. Si une application ne tient pas ce calendrier, mieux vaut le savoir maintenant que le 2 janvier.

Le plus coûteux, dans une montée de version, est de découvrir les blocages — bundles abandonnés, dépendances figées, dette qui empêche Rector d’avancer — à quelques semaines de l’échéance. Nous accompagnons ce type de migration sur des applications en production : cadrage, montée progressive, retour arrière prévu. Voyez comment nous cadrons une migration Symfony si vous voulez chiffrer l’effort avant le 31 décembre.

Cet article vous a été utile ? Partagez-le.

Portrait de Flavien Métivier, fondateur de The Bearded Bear : barbe longue, lunettes, sur fond sombre

Écrit par

Flavien Métivier

Expert PHP/Symfony indépendant depuis 2014, ancien CTO de Kartable et d'Extrabat. Plus de 80 projets menés, de l'audit à la migration.

Mon parcours

À lire ensuite

Migration Symfony en vue ?

Bear Upgrade migre votre codebase 2,5× à 4,5× moins cher qu'une ESN, selon sa maturité.

Newsletter Bear Bytes

Veille Claude Code, Symfony et PHP, retours d'expérience terrain et conseils techniques, directement dans votre boîte mail.