Cet article est une synthèse de la documentation officielle : Forms.
Généralités
Dans Symfony, un formulaire et un champ sont représentés tous deux par la FormInterface
.
Un formulaire contient des champs ou des sous-formulaires enfants, pouvant eux-mêmes en avoir.
(Il s'agit là du Design Pattern Composite.)
Type de champ/formulaire
La FormTypeInterface
permet de représenter un type de champ/formulaire (ex: ContactFormType
,
BirthdayType
, ...). En fonction de ce type, le champ/formulaire aura telles ou telles options, et tel ou tel rendu.
La classe BaseType
l'implémente. En héritent deux sous-classes : FormType
pour les champs et ButtonType
pour
les boutons.
Définition de formulaire
Pour définir un nouveau type de champ/formulaire, il faut donc implémenter FormTypeInterface
.
Pour simplifier cette tâche, on étend AbstractType
, qui implémente déjà les méthodes avec des comportements neutres.
De plus, on surcharge éventuellement sa méthode getParent()
, pour indiquer le type précis dont on veut hériter les
propriétés (ex: EmailType
, EntityType
, ...).
Cette mécanique consistant à ne pas utiliser l'héritage direct PHP des FormTypes qui nous intéresse, évite de
devoir appeler nous-même les méthodes parentes avant d'y ajouter nos traitements spécifiques.
L'appel au parent est fait automatiquement et nous n'avons qu'à nous occuper des traitements spécifiques.
Exemples natifs :
use \Symfony\Component\Form\AbstractType;
// Héritage PHP du type neutre
class DateType extends AbstractType {
public function getParent() {
// Héritage des propriétés/options/... du type de base
return FormType::class;
}
}
// Héritage PHP du type neutre
class BirthDayType extends AbstractType {
public function getParent() {
// Héritage des propriétés/options/... de DateType
return DateType::class;
}
}
// Héritage PHP du type neutre
class FormType extends AbstractType {
public function getParent() {
// Pas d'héritage de propriétés/options/...
return null;
}
}
Extension de type
Pour des modifications légères de types déjà existants (par exemple ajouter des options), on peut implementer
l'interface FormTypeExtensionInterface
.
Création d'un formulaire
Pour créer un formulaire, on utilise un FormBuilder
ou on laisse Symfony deviner les champs en fonction d'une classe.
Pour cela :
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
// Depuis un contrôleur étendant AbstractController
$form = $this->createFormBuilder($task)
->add('task', TextType::class)
->add('dueDate', DateType::class)
->add('save', SubmitType::class, ['label' => 'Create Task'])
->getForm();
// ou en laissant Symfony deviner
$form = $this->createForm(Task::class, $task, $options);
// Depuis n'importe où
/** @var \Symfony\Component\Form\FormFactoryInterface $form */
$form = $this->formFactory
-> createBuilder(Task::class, $task, $options)
->add('task', TextType::class)
->add('dueDate', DateType::class)
->add('save', SubmitType::class, ['label' => 'Create Task'])
->getForm();
// ou en laissant Symfony deviner
$form = $this->formFactory->create(Task::class, $task, $options);
Dans le second cas, Symfony se base sur des Guesser qui devinent les champs à partir :
- du mapping Doctrine (pour les entités)
- du typage PHP des propriétés (>= 7.4)
- des contraintes de validation
Vérification de la requête
FormInterface
définit la méthode handleRequest()
, chargée de vérifier que tout est valide puis éventuellement
d'appeler la méthode submit()
du formulaire :
- Vérification du verbe HTTP (POST, PUT, ...)
- Récupération des données depuis la query ou dans le corps, selon la configuration
- Vérification des paramètres serveur (ex: post_max_size)
- Soumission éventuelle du formulaire
Soumission du formulaire
À la soumission du formulaire (ak. méthode submit()
) :
- les données sont enregistrées dans le formulaire
- chaque champ est validé, selon ses contraintes
- en cas d'erreur, la méthode
addError()
est appelée sur le champ
- en cas d'erreur, la méthode
- les évènements sont dispatchés
Listes des options d'un FormType
La commande suivante permet de lister toutes les options d'un type de champ/formulaire et d'indiquer si elles sont héritées ou surchargées :
# Remplacer EntityType par n'importe quel FormType
bin/console debug:form EntityType
Rendu avec Twig
Il est considéré comme une bonne pratique, de rendre directement un formulaire ou un de ses champs via les fonctions de rendu dédiées. Toute modification du balisage qu'elles génèrent devrait être réalisée via la surcharge de thème.
Par défaut, plusieurs thèmes sont disponibles, notamment Bootstrap 3 et 4. Pour en ajouter :
# config/packages/twig.yaml
twig:
form_themes:
- 'form/my_custom_form_theme.html.twig'
Un formulaire ou un champ dans un template est un objet de type FormView
.
Choix du thème
Il est possible de choisir un thème spécifique pour rendre un formulaire ou un champ, via :
{% form_theme my_form 'form/app_custom_form_theme.html.twig' %}
{% form_theme my_form.my_field 'form/app_custom_form_theme.html.twig' %}
Surcharge du thème
Chaque thème définit un certain nombre de blocs, nommés d'une manière à définir quel type de champ il est susceptible
de rendre (ex: integer_widget
).
Pour le surcharger, il suffit de redéfinir le bloc dans le template courant, comme n'importe quel bloc.
Les suffixes disponibles pour les noms de bloc sont les suivants : _widget
, _row
, _label
, _help
.
Les préfixes disponibles sont listés dans la variable block_prefixes
présente dans le contexte du bloc.
Exemple : ['form','text','textarea', '_post_content']
, pour un champ nommé content
, de type TextAreaType
(héritant de TextType
héritant lui-même de FormType
), au sein du type de formulaire spécifique PostType
.
Data transformers
Un data transformer est chargé de transformer une donnée (sous forme de tableau, objet, entier, ...) vers une chaîne (ou un tableau de chaînes) exploitable dans un formulaire, et vice versa.
Il est représenté par l'interface DataTransformerInterface
et requiert deux méthodes :
transform()
: Donnée => (tableau de) chaîne(s) de caractèresreverseTransform()
: (Tableau de) chaîne(s) de caractères => donnée
Pour en ajouter un à formulaire, on utilise le FormBuilder
:
$formBuilder->addViewTransformer(new MyCustomTransformer());
// Note: il existe également cette méthode si la transformation doit être effectuée en amont de la transformation
// de la précédente
$formBuilder->addModelTransformer(new MyCustomModelTransformer());
Évènements
Tous les évènements liés aux formulaires sont des FormEvent
, permettant d'accéder aux données du formulaire et
éventuellement de les modifier.
Ils ont lieu dans cet ordre chronologique :
-
FormEvents::PRE_SET_DATA
: on peut y modifier la donnée en fonction du formulaire -
Hydratation du formulaire et ses enfants, avec la donnée
-
FormEvents::POST_SET_DATA
: on peut y modifier le formulaire en fonction de la donnée (ex: champ affiché en fonction d'un autre champ) -
FormEvents::PRE_SUBMIT
: on peut y changer la donnée soumise dans la requête (ex: la normaliser, la trimmer, ...) -
Transformation vers un "objet" de données, via les méthodes
reverseTransform()
desDataTransformer
. -
FormEvents::SUBMIT
: les données sont prêtes, mais peut-être pas celles des parents -
FormEvents::POST_SUBMIT
: les données sont prêtes, le composant Validation écoute cet évènement pour les valider
90% du temps, on n'a besoin d'agir que sur les évènements FormEvents::POST_SET_DATA
ou FormEvents::PRE_SUBMIT
uniquement.