Image mise en avant pour l'article

Construire des formulaires vraiment dynamiques avec Symfony et Stimulus

18 août 2025
Symfony
Qui n’a jamais eu envie de créer des formulaires dynamiques ? Imaginons qu’en modifiant la valeur d’un champ, d’autres apparaissent automatiquement (ou dynamiquement) en fonction de ce choix. Dingue, non ? Approchez, que je vous explique comment faire.


Dans cet article nous verrons comment implémenter un formulaire vraiment dynamique. Mais au faite, connaissez-vous la différence entre un formulaire dynamique et un formulaire vraiment dynamique ? La réponse vous attend juste en dessous.

Et pourquoi s’y intéresser ? Les avantages sont nombreux. Mais, pour moi, le principal avantage est de générer et afficher des champs dynamiques sans avoir à anticiper les données que l’utilisateur saisira dans son formulaire. Cet avantage offre à l’utilisateur plus de flexibilité pour ajouter toutes les informations nécessaires au bon fonctionnement de votre application. Prêt ? C’est parti…

Un homme développe et code un site internet avec un formulaire sur Symfony

Les formulaires dynamiques : ce que vous savez déjà

Si vous êtes un développeur Symfony, vous n’êtes pas sans savoir qu’il est possible d’afficher dynamiquement un champ en fonction des valeurs prédéfinies d’un champ cible.

Voici d’ailleurs un exemple tiré de la documentation officielle de Symfony UX Component.

Je ne vais pas m’attarder là-dessus, car l’objectif ici est d’aller plus loin. Grâce à la librairie symfonycasts/dynamic-forms, nous pouvons afficher un champ en fonction d’une valeur choisie. Mais cela suppose que nous devons connaître à l’avance les valeurs que nous avons définies dans le champ parent. Ce qui casse un peu le côté dynamique du formulaire.

Dans ce que nous souhaitons réaliser ici, c’est plutôt d’afficher, en fonction d’un champ dont les valeurs ne sont pas forcément connues à l’avance, des champs enfants qui permettront de modifier des variables associées à ce formulaire.

Un formulaire dynamique, vraiment dynamique

Lorsque j’ai développé cette fonctionnalité, j’ai d’abord cherché des exemples déjà existants. L’idée étant de ne pas réinventer la roue. Mais je n’ai rien trouvé. J’ai donc décidé de vous proposer un exemple fonctionnel et optimisé d’un formulaire vraiment dynamique.

Prenons un exemple concret :
Je souhaite générer un document en me basant sur un gabarit (ou template). Chaque gabarit défini ses propres variables : un titre, un sous-titre, un paragraphe libre, une image de signature, etc. Mais tous les templates ne sont pas composés des mêmes variables, et le formulaire de création de document doit donc s’adapter dynamiquement.

Notre objectif sera d’afficher uniquement les champs correspondant aux variables du gabarit sélectionné, sans recharger la page, tout en gardant un traitement propre côté serveur.

Autrement dit dans un formulaire de création d’un document, nous allons offrir à l’utilisateur, selon le gabarit choisi, la possibilité de créer les variables associées à ce template.

Voilà, après ces explications un peu casse-tête, je vous propose de passer au vif du sujet... c’est-à-dire à la conception technique.

Réalisation d’un formulaire dynamique

Pour réaliser un formulaire dynamique, il faudra installer, au préalable, la librairie suivante : symfonycasts/dynamic-forms. Je vous laisse consulter la documentation d’installation, je ne vais pas tout vous expliquer tout de même.

Une fois l’installation réalisée, nous allons pouvoir créer notre formulaire qui contiendra tout ce qu’il nous faut pour le bon fonctionnement de notre formulaire dynamique. Pour ceci nous allons également utiliser Stimulus pour gérer l’affichage dynamique des champs sur le navigateur. Et évidemment, comme nous faisons du Symfony, nous utiliserons Twig pour les templates HTML.

Les 5 étapes pour construire un formulaire dynamique avec Symfony

article-formulaire-dynamique-symfony-image1

Ce code crée un formulaire Symfony pour l’entité DocumentTemplate (le modèle de document), avec un comportement dynamique basé sur le champ pageTemplate (le gabarit).

Ici, l’objectif est d’afficher uniquement les champs correspondant aux variables définies par le gabarit sélectionné, et de s’assurer qu’à la soumission, seules ces variables soient traitées côté serveur. Procédons par étapes

> Étape 1 – Récupération des données

Cette première étape consiste à extraire les données nécessaires depuis le formulaire et ses options, dans le but de préparer la suite du traitement.

  • Je récupère l’objet DocumentTemplate associé au formulaire.
$documentTemplate = $builder->getData();
  • Je récupère toutes les variables disponibles pour les gabarits ($allVariables), fournies via les options du formulaire.
$allVariables = $options['all_variables'];

> Étape 2 – Ajout du champ pageTemplate avec des attributs dynamiques

Dans cette étape, nous allons configurer un champ avec des attributs personnalisés en fonction des données associées à chaque option.

  • J’ajoute un champ pageTemplate, qui permet de choisir un template.
->add('pageTemplate', EntityType::class, [
    ...
    'choice_attr' => function (PageTemplate $pageTemplate) {
        $variables = $pageTemplate->getPageTemplateVariables()->toArray();
        $codes = array_map(static fn($v) => $v->getCode(), $variables);
        return ['data-variables' => json_encode($codes, JSON_THROW_ON_ERROR)];
    }
])
  • Et pour chaque choix, j’injecte un attribut HTML data-variables contenant les codes des variables attendues pour ce template.

Cet attribut sera ensuite utilisé en JavaScript pour afficher dynamiquement les bons champs côté client.

> Étape 3 – Ajout des champs dynamiques (variables)

Cette étape consiste à ajouter tous les champs potentiels liés aux variables, en amont du filtrage dynamique côté client.

foreach ($allVariables as $variable) {
    ...
    switch ($type) {
        ...
        // Ajoute le champ avec le bon type (TextArea, Fichier, Texte)
    }
}
  • Pour chaque variable disponible, un champ est ajouté à l’avance, quelle que soit sa pertinence pour le pageTemplate sélectionné.
  • Le type de champ est déterminé selon l’enum PageTemplateVariableTypeEnum :
    • TEXTAREA → champ WYSIWYG
    • IMAGE → champ de fichier avec contrainte MIME
    • autre → champ texte simple
  • Chaque champ est ajouté avec un attribut data-variable-code, qui servira ensuite au filtra dynamique côté client.

> Étape 4 – Lien de dépendance avec DynamicFormBuilder

Dans cette quatrième étape, nous établissons une dépendance logique pour permettre à l’interface de réagir dynamiquement aux changements de sélection.

$builder->addDependent(
    'variables',
    'pageTemplate',
    function (...) { ... }
);
  • Je déclare donc une dépendance dynamique : les variables dépendent du pageTemplate.
  • La callback est vide, car tous les champs ont déjà été ajoutés avant. Mais cela permet à la bibliothèque JS de gérer l’affichage.

> Étape 5 – Nettoyage dynamique avant soumission (PRE_SUBMIT)

Et dans cette dernière étape, nous épurons dynamiquement les champs du formulaire selon le gabarit sélectionné, juste avant la validation.

$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
    ...
    foreach ($form->all() as $name => $child) {
      ...
      if (!in_array($name, $validCodes)) {
        $form->remove($name);
      }
    }
});
  • À la soumission, Symfony exécute ce listener avant la validation.
  • Il récupère le pageTemplate sélectionné.
  • Il identifie les codes valides des variables associées à ce template.
  • Il supprime dynamiquement tous les champs qui ne sont pas pertinents, afin d’éviter des erreurs de validation ou de stocker des données inutiles.

Pourquoi ce code ?

Ce code permet de gérer dynamiquement des formulaires Symfony adaptables à différents templates, tout en garantissant performance, maintenabilité et compatibilité avec une logique de rendu côté client. Il présente notamment les avantages suivants :

  • Offrir un formulaire unique et générique pour tous les gabarits, capable de s’adapter dynamiquement.
  • Précharger tous les champs nécessaires pour l’affichage dynamique côté Javascript, mais les filtre proprement côté serveur à la soumission.
  • Associer dynamiquement des blocs de contenu variables à un modèle, sans créer un formulaire spécifique par template.

Pour ajouter ces champs dynamiques, j’ai testé plusieurs manières. De l’AJAX pour construire le HTML correspondant ? Mais, cette solution ne fonctionne pas car je ne peux pas ajouter les champs dynamiquement côté serveur et garder une bonne validation du formulaire. C’est pour cette raison que j’ai préféré tous les ajouter dès le départ, puis supprimer ceux qui sont inutiles. Ce qui permet de conserver la validation native de Symfony, sans erreurs ni contournements.

Maintenant, nous allons passer au JavaScript. Sans lui, tous les champs s’afficheraient simultanément à l’écran. Nous devons donc déterminer quels champs afficher dynamiquement. Le JavaScript se chargera de n’afficher que les champs pertinents, en fonction du template choisi.

Afficher les champs dynamiquement avec JavaScript et Stimulus

article-formulaire-dynamique-symfony-image2

Le contrôleur Stimulus gère l’affichage des champs « variables » selon le page template choisi :

> Déclaration des cibles

Nous déclarons quelques cibles sur lequel notre Javascript va pouvoir se baser.

  • pageTemplateTarget : représente le champ <select> où l’utilisateur choisit le template.
  • variableFieldTargets : correspond à la liste des blocs HTML contenant chacun un champ variable.

> Méthode connect()

Cette méthode est appelée automatiquement à l’initialisation du contrôleur.

  • Ajout d’un écouteur change sur le champ pageTemplate pour réagir à chaque changement de sélection.
  • Appel immédiat de updateVisibleFields() pour adapter l’affichage dès le chargement de la page (utile en mode édition).

> Méthode updateVisibleFields()

Cette méthode constitue le cœur du comportement dynamique.

  • Récupération de l’option sélectionnée dans le <select>.
  • Lecture de l’attribut data-variables de cette option, qui contient un tableau JSON avec les codes des variables attendues (ex. ["TITLE", "SUBTITLE"]).
  • Parcours de chaque champ dynamique (variableField) :
    • Si le code du champ est dans la liste des variables du template, il est affiché.
    • Sinon, il est masqué en appliquant style.display = 'none'.

> Résultat

Ce mécanisme assure un affichage du formulaire en temps réel, adapté au template sélectionné, sans recharger de la page et sans avoir à recréer ni manipuler les champs côté serveur.

Et pour finir le HTML/Twig correspondant 😉

Capture d'écran du code HTML/Twig

Pas de panique, je vais vous expliquer.

> Objectif général

Ce template Twig génère un formulaire Symfony dans lequel certains champs s’afffichent dynamiquement, toujours selon le gabarit sélectionné, grâce à la liaison avec le contrôleur Stimulus.

> Démarrage et fin du formulaire

Cette section initialise le formulaire et prépare le terrain pour le fonctionnement dynamique des champs dépendants.

  • Ouverture et fermeture du formulaire Symfony.
  • Ajout de l’attribut data-dependent-form pour indiquer que le formulaire contient des champs dépendants (dynamiques)

> Champ « pageTemplate »

Ce champ permet à l’utilisateur de choisir un gabarit, tout en étant connecté au système de gestion dynamique du formulaire.

  • Affiche le champ de sélection du template de page.
  • L’attribut data-dynamic-form-target="pageTemplate" relie ce champ au contrôleur Stimulus, qui écoutera ses changements.

> Champs dynamiques « variables »

Cette section regroupe tous les champs variables, prêts à être affichés ou masqués dynamiquement par le JavaScript.

  • Parcourt tous les champs du formulaire sauf ceux explicitement exclus (non dynamiques).
  • Chaque champ variable est enveloppé dans un <div> avec :
    • data-dynamic-form-target="variableField" : permet au contrôleur JS de le reconnaître comme champ dynamique.
    • data-variable-code : contient le nom du champ, utilisé par le JS pour déterminer s’il doit être affiché ou non.

Résultat final

Cette organisation donne au JavaScript la possibilité d’identifier facilement les champs dynamiques et de contrôler leur affichage en fonction du template sélectionné, rendant ainsi le formulaire adaptatif et fluide pour l’utilisateur.

Au final, la combinaison de ces trois bouts de code (Symfony, Stimulus et Twig) aboutit à un formulaire complètement dynamique.

Démonstration du résultat d'un formulaire dynamique

Oui vous avez bien vu, tous les champs sont initialement affichés et ensuite masqué par le JavaScript. Mais grâce à la partie PHP, nous pouvons nous assurer du bon fonctionnement du formulaire. Les validations côté serveur sont parfaitement fonctionnelles, et il n’y a aucun risque que l’utilisateur saisisse n’importe quoi.


Tout au long de cet article, nous avons vu comment construire un formulaire vraiment dynamique. Je rappelle que l’utilité de ce type de formulaire est d’afficher des champs sans connaître/prévoir à l’avance le choix ou les réponses de l’utilisateur.
Maintenant, à vous de jouer ! J’espère que tout se passera bien...

Et si vous avez encore un peu de courage, je vous conseille de lire cet article : « Symfony UX : à la rencontre des Twig Components ».

Crédit photo : Rneaw

Image mise en avant pour l'article
Matthieu Fritsch
Développeur Web • Pôle Framework & DevOps
Vous avez un projet digital (site internet, e-commerce, intranet, application métier, etc. ) à nous confier ?
Nos experts sont à vos côtés pour vous conseiller et vous accompagner à chaque étape de votre projet.
Contactez-nous !