Symfony 8.2, attendu en novembre 2026, réduit le coût du Serializer, du cache et du warmup sans modifier le code applicatif. Le billet officiel du 2 octobre 2026 décrit le résultat de quelques semaines pendant lesquelles Nicolas Grekas a profilé la Symfony Demo et un projet d'API sur mesure, en transformant chaque trouvaille en pull request. Toutes ces PR sont mergées dans la branche 8.2.
Un des changements n'est pas une optimisation : les erreurs client (4xx) passent du niveau de log error au niveau warning. Si une alerte de ton monitoring repose sur error, elle peut se taire à la montée de version. Cet article trie ce qui compte, dit ce que chaque chiffre mesure, et donne la vérification à faire avant de migrer. Le dispatcher d'événements compilé, autre optimisation de la même version, a son propre article dans la série : on n'y revient pas ici.
Comment lire ces chiffres sans se raconter d’histoires
- Deux bancs d’essai précis : la Symfony Demo, et une API non publique avec des listes de 150 à 900 objets, groupes de sérialisation et name converter. Ils ne se transposent pas tels quels à ton application.
- Instructions n’est pas temps : plusieurs gains sont exprimés en nombre d’instructions PHP, d’autres en microsecondes, d’autres en temps de requête.
- Pas de chiffre global : le billet ne donne aucun gain total de temps de réponse. N’en invente pas, et mesure sur ton code, avant et après.
Serializer et API : jusqu’à 24 % plus rapide sur les listes
Si tu exposes des listes d'objets, c'est ta section. Le Serializer calcule désormais le contexte de chaque attribut une seule fois, évite de copier le tableau normalisé à chaque attribut, et récupère le mapping de discriminant une fois par objet plutôt qu'une fois par attribut. Sur l'API de test, les routes de liste sont 14 % à 24 % plus rapides (PR #66383). C'est le chiffre le plus parlant du billet pour une API, et c'est un résultat de bout en bout.
- PropertyAccess lit une propriété avec 40 % d’instructions PHP en moins, soit 6 à 8 % d’instructions en moins sur les mêmes routes de liste (PR #66397).
- Le name converter
snake_casemémoïse ses résultats au lieu d’exécuter une expression régulière par attribut et par objet : 2 à 5 % de gain (PR #66384). - Côté dénormalisation (typiquement
#[MapRequestPayload]), les paramètres de constructeur sont vérifiés avant d’appeler PropertyInfo : 4 à 6 % d’instructions en moins sur un POST (PR #66399). - Les infos de propriétés du Serializer et du Validator sont préchauffées dans un seul fichier de cache : les POST gagnent 4 à 5 % en production (PR #66386). L’effet se matérialise au
cache:warmupde ton déploiement.
En développement, l'écart est plus net : les docblocks de constructeur sont parsés une fois par classe au lieu d'une fois par propriété, et comme PropertyInfo n'est pas mis en cache en mode debug, les POST en dev gagnent 24 à 38 %, soit 10 à 14 ms chacun (PR #66382). C'est un gain de confort, pas de production.
Pourquoi les logs 4xx passent en warning : l’impact sur tes alertes
Depuis Symfony 8.2, les réponses 4xx sont journalisées au niveau warning au lieu de error (PR #66391). L'argument est cohérent : un 404 ou un 422 est une erreur du client, pas de l'application. Le gain aussi : avec la recette Monolog de production, un handler fingers_crossed dont l'action_level est error, chaque 400, 403 ou 422 déclenchait l'écriture de tout le buffer de logs de la requête. Un client pouvait ainsi remplir tes logs à volonté. Un 422 issu de #[MapRequestPayload] devient 3 à 4 % plus rapide.
Le billet et la PR semblent se contredire sur l'action_level par défaut, mais ils décrivent deux choses différentes. Le défaut de MonologBundle est warning ; c'est la recette monolog.yaml qui le surcharge à error en production. Seuls les handlers réglés explicitement à error, comme celui de la recette, cessent d'être activés par un 4xx. Voici l'extrait de prod de la recette, le cas typiquement concerné :
# config/packages/monolog.yaml (recette, extrait when@prod)
when@prod:
monolog:
handlers:
main:
type: fingers_crossed
action_level: error # un 4xx (désormais warning) ne l'active plus
handler: nested
excluded_http_codes: [404, 405]
channels: ["!deprecation"]
buffer_size: 50
nested:
type: stream
path: php://stderr
level: debug
formatter: monolog.formatter.jsonCe que ça change concrètement :
- Une alerte câblée sur le niveau
errorne verra plus les 4xx. Si tu t’en servais, volontairement ou non, pour détecter un pic de 403 ou de 422, elle se tait sans bruit. - Avec
fingers_crossedàerror, une requête qui finit en 422 n’écrit plus son buffer de debug : tu perds ce contexte dans tes logs de prod. - Les 5xx et les exceptions non HTTP restent en
critical: ton canal d’alerte « vraies pannes » n’est pas affecté.
Le niveau reste configurable par exception, via framework.exceptions ou l'attribut #[WithLogLevel], et un niveau explicite l'emporte sur le nouveau défaut. Exemple maison, à adapter, pour garder une exception métier alertable malgré son statut 422 :
<?php
declare(strict_types=1);
namespace App\Exception;
use Psr\Log\LogLevel;
use Symfony\Component\HttpKernel\Attribute\WithHttpStatus;
use Symfony\Component\HttpKernel\Attribute\WithLogLevel;
// 422 pour le client, mais on veut toujours être alerté (niveau error)
#[WithHttpStatus(422)]
#[WithLogLevel(LogLevel::ERROR)]
final class PaymentRejectedException extends \DomainException
{
}Pour Sentry, tout dépend de ton intégration : si le SDK capture les exceptions par son propre listener, rien ne change ; s'il passe par un handler Monolog réglé sur error, les 4xx disparaissent. Vérifie avant de conclure.
Migration Symfony en vue ?
Bear Upgrade migre votre codebase 2,5× à 4,5× moins cher qu'une ESN, selon sa maturité.
Quels gains en production sans toucher au code ?
- PhpFilesAdapter n’essaie plus d’inclure des fichiers inexistants. Sur la Symfony Demo, avec un répertoire de cache en lecture seule et sans APCu, cela économise 830 µs par requête (PR #66347). Si tu as APCu, ce chiffre ne s’applique pas tel quel.
- ChainAdapter::get() retourne directement les hits de son premier adapter, sans créer deux closures à chaque appel.
- Le runtime mode n’est plus résolu sur les requêtes web : 8 des 18 lectures de variables d’environnement d’une requête de production de la Demo disparaissent.
- HtmlSanitizer ne parse plus les entrées sans
<ni&: nettoyer un texte court devient 5 fois moins coûteux et n’alloue plus 1,1 Mo par appel. Utile avec l’optionsanitize_htmldes champs de formulaire.
Ces gains sont réels, et ils se comptent en microsecondes. Ils restent petits devant une requête SQL ou un appel HTTP externe, ce que le billet reconnaît lui-même : on ne trouve pas de changement magique, on gagne microseconde par microseconde.
Dev, cache:warmup et conteneur : des déploiements et un feedback plus rapides
Tous les chiffres suivants sont mesurés sur la Symfony Demo :
- Traductions : éditer un fichier de traduction ne reconstruit plus le conteneur. La requête suivante passe d’environ 790 ms à 85 ms (PR #66332). C’est le chiffre le plus parlant pour ta boucle de développement.
- Profiler : la réponse est envoyée avant que le profiler sauvegarde ses données ; avec PHP-FPM, les pages de dev se terminent 11 à 13 ms plus tôt (PR #66330).
- XLIFF : validation préalable contre un schéma plus petit ; un
cache:warmupà froid passe de 1,77 s à 1,57 s. - Twig : le warmer ne compile que les form themes utilisables par l’application ; warmup de 2,27 s à 2,11 s.
- Dump du conteneur en debug : seules les définitions qui contiennent des variables d’environnement sont clonées ; l’étape passe de 49 ms à 14 ms.
On parle de dixièmes de seconde par déploiement. Sur une CI qui rejoue ce warmup des dizaines de fois par jour, cela s'additionne ; sur un déploiement par semaine, ça ne changera rien.
Et en worker mode (FrankenPHP, RoadRunner) ?
Beaucoup de ces optimisations reposent sur le calcul à la demande. Un lecteur a demandé sous le billet si le worker mode en pâtirait. Nicolas Grekas répond que non : ce qui est construit à la première utilisation reste en mémoire pour toute la vie du worker, comme avant. Le worker mode ne devient pas plus lent, mais la plupart de ces gains visent le coût d'amorçage par requête, celui que paie PHP-FPM. Si tu tournes déjà en worker mode avec FrankenPHP, ton gain sera plus modeste.
Ce qui remonte jusqu’à PHP, et ce qui reste en revue
Ce travail a débordé de Symfony. Twig parcourt ses nœuds plus vite, TwigComponent pré-lexe ses templates plus rapidement, et côté PHP, new ArrayIterator() et new ArrayObject() exécutent environ 180 instructions de moins par appel (php-src #23946, mergée ; la version de PHP qui l'embarquera n'est pas précisée). En revanche, plusieurs propositions restent en revue et ne sont pas acquises : htmlspecialchars() qui retourne son entrée quand il n'y a rien à encoder, le copy-on-write pour ArrayObject, ou une allocation réduite dans Lexbor. Ne les compte pas dans ton gain.
Faut-il upgrader pour la performance ?
Ces optimisations valent la montée de version, mais pas pour la raison que le titre suggère. Elles ne changeront pas le profil de performance d'une application : une route lente le restera, et tu gagneras quelques pourcents sur la partie framework de la requête. Leur vraie valeur est ailleurs : une boucle de dev plus rapide, un warmup plus court, et une sérialisation nettement moins coûteuse si ton API renvoie de grosses listes. Avant de passer à 8.2 :
- Liste tes alertes basées sur le niveau
erroret repère celles qui dépendent de 4xx : elles se tairont. - Ouvre ton
monolog.yamlet vérifie l’action_levelréel de ton handlerfingers_crossed. - Fixe le niveau explicitement pour les exceptions que tu veux garder visibles, avec
framework.exceptionsou#[WithLogLevel]. - Mesure avant et après sur ta propre application, par exemple en traçant tes requêtes avec OpenTelemetry. Sans mesure, tu ne sauras pas ce que tu as gagné.
Le plus coûteux dans une montée en 8.2 ne sera sans doute pas le temps de chargement des pages, mais ce qui change sans bruit, comme une alerte qui ne sonne plus sur un 422. C'est le genre de point que Bear Upgrade traite en amont : la migration arrive en Merge Requests testées, prêtes à merger, avec les changements de comportement repérés avant la mise en production plutôt que découverts après.

É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