Le blog

Sortie de Symfony 8.0 

Publié le 06 novembre 2025

La version 8.0 de Symfony est prévue pour fin novembre, presque en même temps que PHP 8.5. Comme à chaque nouvelle version, une autre édition verra également le jour : la 7.4 LTS (Long Term Support). Symfony 8 introduit de nouvelles fonctionnalités, quelques dépréciations, et surtout un composant très attendu par la communauté. Voyons ensemble les principales nouveautés à retenir !

Sortie Symfony 8.0" class="wp-image-11834"/><figcaption class="wp-element-caption#

Préambule

Symfony 7.3 exigeait à minima PHP 8.2. La nouvelle version majeure de Symfony ira plus loin : elle aura besoin au moins de PHP 8.4 pour fonctionner. Pourquoi ce changement ? 

  • Pour la sécurité : Symfony veut être certain de “tourner” sur des versions de PHP encore supportées. Ainsi, le framework abandonne les anciennes versions du langage qui ne reçoivent plus de correctifs de sécurité. Cela encourage aussi à rester à jour.
  • Pour être plus moderne et rapide : En exigeant une version récente de PHP, Symfony peut utiliser les toutes dernières fonctions du langage, le rendant lui-même encore plus performant et à la page.
#

Arrivée d’un nouveau composant majeur : FormFlow

Symfony 8.0 intègre un nouvel outil nommé FormFlow, qui facilite la création de formulaires complexes.

L'idée vient d'un ancien bundle CraueFormFlowBundle et du travail de la communauté. Cet ajout permet gérer les formulaires comportant plusieurs étapes ou plusieurs pages. On pense ici à des formulaires "longs" comme une inscription ou bien un outil de configuration de produit.

FormFlow permet de se concentrer sur chaque étape, une à une, sans avoir à écrire tout le code répétitif nécessaire pour :

  • Savoir à quelle étape se trouve l'utilisateur.
  • Gérer les boutons "Suivant" et "Précédent".
  • Stocker les informations entre chaque page.

FormFlow étend le composant Form. La mise en place est très intuitive. Au lieu de créer un AbstractType, on crée un AbstractFlowType chargé d’orchestrer l'ensemble du processus.

  • On n'utilise pas buildForm() mais buildFormFlow().
  • On ajoute les différentes étapes avec addStep(). Chaque étape est un formulaire "classique" (un AbstractType).
  • On ajoute un système de navigation, par exemple le NavigatorFlowType fourni par défaut.
use Symfony\Component\Form\Flow\AbstractFlowType;
use Symfony\Component\Form\Flow\FormFlowBuilderInterface;
use Symfony\Component\Form\Flow\Type\NavigatorFlowType;

class UserSignUpType extends AbstractFlowType // Au lieu du AbstractType
{
    public function buildFormFlow(FormFlowBuilderInterface $builder, array $options): void
    {
        // Étape 1 : intègres un formulaire 'UserSignUpPersonalType'
        $builder->addStep('personal', UserSignUpPersonalType::class);
        
        // Étape 2 : intègres un formulaire 'UserSignUpProfessionalType'
        $builder->addStep('professional', UserSignUpProfessionalType::class);

        // Étape 3 : intègres un formulaire 'UserSignUpAccountType'
        $builder->addStep('account', UserSignUpAccountType::class);

        // Étape 4 : ajoute les boutons "Précédent", "Suivant", "Terminer"
        $builder->add('navigator', NavigatorFlowType::class);
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => UserSignUp::class,
            // 'step_property_path' est la clé : 
            // il indique quelle propriété de votre objet (UserSignUp)
            // stocke le nom de l'étape actuelle.
            'step_property_path' => 'currentStep',
        ]);
    }
}

La gestion dans le contrôleur est presque identique à celle d'un formulaire classique. Parmi les points communs, on crée le formulaire avec $this->createForm(), et on le soumet avec $flow->handleRequest($request).

Au niveau des différences, au lieu d’utiliser $form->isValid(), on vérifie si le flux est terminé avec $flow->isFinished() et pour l'affichage, on n'envoie pas $flow à notre template Twig, mais $flow->getStepForm(). Ce dernier contient uniquement le formulaire correspondant à l'étape active.

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

class UserSignUpController extends AbstractController
{
    #[Route('/signup')]
    public function __invoke(Request $request): Response
    {
        $flow = $this->createForm(UserSignUpType::class, new UserSignUp())
            ->handleRequest($request);

        if ($flow->isSubmitted() && $flow->isValid() && $flow->isFinished())        
 {
            // ... enregistrer en BDD, etc.

            return $this->redirectToRoute('app_signup_success');
        }

        // On passe à Twig uniquement le formulaire de l'étape en cours
        return $this->render('signup/flow.html.twig', [
            'form' => $flow->getStepForm(),
        ]);
    }
}

Le composant FormFlow était très attendu car il apporte une grande praticité au développement. Il permet de :

  • Garder les données en mémoire : Les informations saisies sont automatiquement conservées entre chaque page. Lorsque l'utilisateur revient en arrière, ses réponses précédentes sont toujours là.
  • Validation étape par étape : FormFlow ne valide que les champs de la page courante. Il suffit de définir quelles règles s’appliquent à chaque étape, et FormFlow s’occupe du reste. Fini les erreurs sur des champs qui ne sont pas encore affichés !
  • Boutons de navigation faciles : FormFlow propose des boutons spéciaux tels que "Suivant", "Précédent" et "Terminer", utilisables tels quels ou facilement personnalisables. Il est également possible d’ajouter des boutons supplémentaires, comme Passer cette étape ou Retour au début.
  • Afficher où en est l'utilisateur : FormFlow indique la progression de l'utilisateur, ce qui facilite la mise en place d’une barre de progression (par exemple : "Étape 2 sur 5") sur votre page.

Pour en savoir plus sur ce composant, vous pouvez consulter le replay de sa présentation lors de la SymfonyOnlineJune 2025.

#

Trois composants expérimentaux deviennent stables

Les trois composants "expérimentaux" dans la version 7.3 sont maintenant stables depuis la version 7.4 (PR #61500). Vous pouvez les utiliser en toute confiance grâce à la “Backward Compatibility Promise”.

Il s’agit de :

  1. JsonPath, qui permet d’interroger et d’extraire des données à partir de structures JSON (contribué par notre coopérateur Alexandre Daubois)
  2. JsonStreamer, qui facilite l’encodage et le décodage des objets PHP en JSON, de manière plus performante. Il est particulièrement utile lorsqu’on doit gérer de gros volumes de données.
  3. ObjectMapper, simplifie le mapping entre objets et DTO. Il est utilisé depuis plusieurs semaines dans le core d’API Platform (contribution d’Antoine Bluchet).

Retrouvez notre article sur la sortie de Symfony 7.3.

#

Les améliorations 

#
Adieu les bugs de fuseau horaire

Cette release permet de corriger un vieux problème de PHP : les dates à un "instant T", en clair, les dates sensibles aux fuseaux horaires. L’explication simple :

  • Le problème : PHP mélangeait un "moment précis" (ex : maintenant, qui change avec le fuseau horaire) et une "date de calendrier" (ex : un anniversaire, qui est fixe). Concrètement, une date de naissance comme le "15 mai 1990" pouvait se changer en "14 mai 1990 à 23h" à cause du fuseau horaire du serveur.
  • La solution : Symfony a créé un nouveau système (à l’instar du composant Clock) pour gérer ces dates flottantes. Maintenant, un anniversaire reste un anniversaire, peu importe le fuseau horaire.
#
Les améliorations récentes

1. Côté Formulaire (PR #60315) La nouvelle option input: 'date_point' (pour DateType, TimeType, DateTimeType) :

  • DayPoint : Pour une date (ex : 2025-10-29).
  • TimePoint : Pour une heure (ex : 09:30:00).

2. Côté Doctrine (PR #60237, #59889), de nouveaux types font le lien avec la base de données pour stocker ces objets :

  • DatePointDateType : DayPoint → Colonne DATE (pour une date de naissance par exemple).
  • DatePointType : DayLoopint (avec heure) → Colonne DATETIME (pour un rendez-vous par exemple).
  • TimePointType : TimePoint → Colonne TIME (pour une heure d'ouverture par exemple).

Fini donc les Data Transformers manuels ! La gestion des dates peut désormais être configurée du formulaire jusqu’à la base de données, et les types plus précis permettent, sans effort supplémentaire, de savoir exactement quel type d’information est manipulé.

#
Simplification des commandes

Vous vous souvenez des "commandes invocables" introduites avec Symfony 7.3 ? Elles continuent d’évoluer avec l’ajout de deux nouveaux attributs #[Interact] et #[Ask] (contribution #61748). Ces attributs servent de raccourcis pour simplifier les interactions avec l’utilisateur. 

#
Mises à jour pour les Uuid (identifiants uniques)
  • UuidV7 devient la norme : Dans les nouvelles versions, Symfony utilise désormais la version UuidV7 par défaut (PR #61812).
  • Plus précis : la version V7 est également plus précise, capable de gérer le temps à la microseconde (PR #60898, ce qui lui permet de respecter la RFC 9562.
  • Des tests plus simples : Un nouvel outil pour les tests, MockUuidFactory, a été ajouté (PR #61807). Fonctionnant comme le MockClock du composant Clock, il permet de fixer les UUID générés pendant les tests. C’est beaucoup plus pratique, car on sait exactement quel identifiant sera utilisé, plutôt que d’en obtenir un nouveau au hasard à chaque exécution.
#
Une nouvelle règle de validation pour les vidéos

Le composant Validator de Symfony sert à vérifier que les données sont correctes. Il possède déjà de nombreuses contraintes de validation mais il en manquait une assez importante pour vérifier les fichiers vidéo. Ce manque est désormais comblé grâce à la contrainte Video.

C'est la même idée que la contrainte Image, que l'on utilise depuis des années pour vérifier qu'un fichier est bien une image, selon des critères définis.

Avec cette nouvelle règle Video, vous pouvez vérifier automatiquement si un fichier est une vidéo valide. Vous pouvez aussi, par exemple, ajouter des conditions sur le poids (taille max du fichier en Mo), la taille (largeur et hauteur max de la vidéo) ou encore le type de fichier (les types MIME, comme video/mp4 ou video/avi). Important : Pour que cette règle fonctionne, vous devez installer FFmpeg.

#
ObjectMapper permet le mapping des collections

L'ObjectMapper peut désormais mapper des collections et des tableaux. Dans le cas où l’on avait un objet qui contient un iterable ou une Collection, il fallait faire le mapping à la main, comme ceci :

<?php
// src/DTO/ProductListInput.php (en 7.3)

use App\Entity\ProductList; 
use App\ObjectMapper\ProductCollectionTransformer;
use Symfony\Component\ObjectMapper\Attribute\Map;

#[Map(target: ProductList::class)]
class ProductListInput
{
    /**
     * @var ProductInput[]
     */
    // On dit au mapper : "Pour cette propriété,
    // utilise ce service pour faire la transformation"
    #[Map(transform: ProductCollectionTransformer::class)]
    public array $products;
}

Le Transformer manuel ressemblait à ça :

<?php

use App\DTO\ProductInput;
use App\Entity\Product;
use Symfony\Component\ObjectMapper\ObjectMapperInterface;
use Symfony\Component\ObjectMapper\TransformCallableInterface;

class ProductCollectionTransformer implements TransformCallableInterface
{
    public function __construct(
        private readonly ObjectMapperInterface $mapper
    ) {}

    /**
     * @param Product[] $value
     * @return ProductInput[]
     */
    public function __invoke(mixed $value, object $source, ?object $target): mixed
    {
        if (!is_array($value)) {
            return [];
        }

        return array_map(
            fn(Product $product) => $this->mapper->map($product, ProductInput::class),
            $value
        ); // ou foreach par exemple
    }
}

Désormais, plutôt que de reproduire ce comportement “à la main”, vous pouvez utiliser le transformer MapCollection :

use Symfony\Component\ObjectMapper\Attribute\Map;
use Symfony\Component\ObjectMapper\Transform\MapCollection;

#[Map(target: Product::class)]
class ProductListInput
{
    #[Map(transform: new MapCollection())]
    /** @var ProductInput[] */
    public array $products;
}

À noter qu’avec cette configuration, ObjectMapper applique les règles de mappage à chaque élément du tableau $products. Plus besoin de réaliser le parcours de votre iterable pour mapper les sous-éléments !

⚠️ Sans la transformation new MapCollection(), le tableau reste inchangé. Cette contribution a été réalisée par Antoine Bluchet.

#
Méthode Request::get() supprimée 

Une ancienne méthode, Request::get(), disparaît. Elle est dépréciée en Symfony 7.4 et supprimée en 8.0 (PRs #61948 et #61983). Voyons pourquoi et par quoi la remplacer.

Contexte : "L'origine est floue". La méthode est considérée comme "dangereuse" car elle "floute l'origine de la donnée". En effet, $request->get('id') cherchait id partout :

  1. Attributs de route (/user/{id})
  2. Paramètres d'URL (?id=123)
  3. Données POST (<input name="id">)

Cela entraîne deux problèmes majeurs :

  1. Code ambigu : On ne sait pas d'où vient la donnée.
  2. Faille de sécurité : Un ?id=abc dans l'URL peut écraser un {id}=123 de la route (Pollution de Paramètres HTTP).

La solution désormais est de spécifier clairement l’origine de vos données.

Avant (ambigu) :

$id = $request->get('id'); 
$page = $request->get('page'); 
$email = $request->get('email');

Après (explicite et sécurisé) :

// Pour les paramètres de route : 
$id = $request->attributes->get('id'); 

// Pour les paramètres d'URL (query string) : 
$page = $request->query->get('page'); 

// Pour les données de formulaire (POST) :
$email = $request->request->get('email');

L'approche recommandée est de ne plus utiliser Request et utiliser les "Argument Resolvers" pour que Symfony vous injecte directement les valeurs typées.

// Fini, le "$request->..." !

#[Route('/user/{id}', methods: ['POST'])]
public function update(
    int $id, // 1. Injecte le paramètre de route
    #[MapQueryParameter] ?string $source = null, // 2. Injecte le paramètre d'URL
    #[MapFormParameter] string $email // 3. Injecte la donnée POST
): Response
{
    // $id, $source, et $email sont prêts à l'emploi.
    // C'est propre, typé et sécurisé.
    ...
}

💡Pour vos APIs JSON, utilisez #[MapRequestPayload] pour mapper directement le JSON sur un DTO. En résumé : ce changement vous encourage à écrire un code plus sûr et moderne. Il est temps de mettre à jour vos projets !

#

Nouveautés de configuration 

#
Fini le XML et l'ancien format PHP

Symfony abandonne deux anciennes façons d'écrire la configuration :

  • Le XML est supprimé car trop long, compliqué à lire et n'est presque pas utilisé (à l’exception de bundles Symfony, qui l'utilisent pour leur configuration interne). C’était par exemple le cas du framework API Platform jusqu'à cette PR mergée récemment.
  • L'ancien format PHP "fluent" est abandonné car il n’était pas compatible avec Symfony Flex.
#
Le renouveau du format PHP

Une nouvelle syntaxe PHP fait son apparition. Elle est :

  • Clé/valeur : similaire à la syntaxe YAML.
  • Compatible Flex : les recettes peuvent maintenant s'appliquer aux fichiers de config PHP.
  • Puissante : elle toute l'autocomplétion et l'analyse statique de votre IDE.

C’est aussi le nouveau format utilisé en interne pour API Platform à partir de la version 4.2.

#
Autocomplétion en YAML

Le YAML, format de configuration par défaut, devient "intelligent". Grâce à l'intégration progressive de JSON Schema, notamment avec la PR #61564 et la collaboration avec PhpStorm, il est désormais possible de bénéficier de l'autocomplétion complète des clés et de la documentation au survol.

#
Autres nouveautés notables
  • FrankenPHP plus simple, contribution d’Alexandre Daubois (PR #60503)
  • Nouveau "dossier partagé" (#62170), utile pour les gros projets qui ont plusieurs applications. 
  • Configuration simplifiée (#61545 et #61563) : Les attributs #[ExtendsValidationFor] et #[ExtendsSerializationFor] sont sortis et permettent d'ajouter des règles (validation, sérialisation) à une classe même si celle-ci n’est pas modifiable. 
  • Meilleures Exceptions dans le Terminal (#60033), pour améliorer le rendu des erreurs et des exceptions levées dans nos commandes. 
  • Le composant Workflow (PR #60201) peut maintenant gérer la "quantité" au lieu de la simple priorité. L'idée est inspirée des Réseaux de Petri
  • Les Helpers de Controller (#60857) sont désormais disponibles de manière autonome et peuvent maintenant être injectées et utilisées n'importe où.
  • Visualisation de la Sécurité en Mermaid (#61034), très utile pour documenter et comprendre visuellement des configurations de sécurité complexes. Une PR est également en cours pour ajouter cela dans le profiler.
  • Des améliorations sur les Voters : Les "Security Voters" de Symfony 7.4 s'améliorent sur deux fronts, à savoir, plus de transparence dans Twig et plus de flexibilité pour la logique complexe.
#

Passez à l’étape supérieure avec Symfony

Du haut de ses 20 ans, Symfony montre qu’il est bien plus qu’un framework : c’est un écosystème vivant, riche de centaines de packages, bundles et composants, capable d’alimenter aussi bien un CMS qu’une architecture de plateforme à grande échelle. Avec la version 8, Symfony continue de tracer sa voie entre innovation et fiabilité. Êtes-vous prêt·es à migrer et à explorer dès aujourd’hui les nouvelles fonctionnalités de la 8.0 ? Notre équipe, dont certains membres participent directement au maintien des composants du framework, vous accompagne à chaque étape de vos projets d’évolution avec Symfony. Contactez-nous pour en discuter !

Vincent Amstoutz

Vincent Amstoutz

Senior backend developer

Mots-clés59889, 60212, 60237, 60315, Release, Symfony, Symfony 8.0

Le blog

Pour aller plus loin