Programmation PHP avec Symfony/Formulaire
Principe
modifierLe principe est d'ajouter des champs de formulaire en PHP, qui seront automatiquement convertis en code HTML correspondant.
En effet, en HTML on utilise habituellement la balise <form>
pour afficher les champs à remplir par le visiteur. Puis sur validation on récupère leurs valeurs en PHP avec la superglobale $_REQUEST
(ou ses composantes $_GET
et $_POST
). Or ce système ne fonctionne pas en $_POST
dans Symfony : si on affiche un tel formulaire et qu'on le valide, $_POST
est vide, et l'équivalent Symfony de $_REQUEST
, $request->request
[1] aussi.
Les formulaires doivent donc nécessairement être préparés en PHP.
Installation
modifierForm
modifier
composer require symfony/form
Les formulaires présents sont ensuite listables avec :
bin/console debug:form
Et vérifiables individuellement :
bin/console debug:form "App\Service\Form\MyForm"
Avec le composant maker, on peut créer un formulaire pour chaque entité Doctrine à modifier :
composer require symfony/maker-bundle bin/console make:form
Validator
modifierPour ajouter des contrôles sur les champs, il existe un deuxième composant Symfony[2] :
composer require symfony/validator
Contrôleur
modifierInjection du formulaire dans un Twig
modifierclass HelloWorldType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('name', TextType::class)
->add('save', SubmitType::class)
;
}
}
class HelloWorldController extends AbstractController
{
#[Route('/helloWorld/{id}, requirements: ['id' => '\d*']')]
public function indexAction(Request $request, ?HelloWorld $helloWorld = null): Response
{
$form = $this->createForm(HelloWorldType::class, $helloWorld);
return $this->render('helloWorld.html.twig', [
'form' => $form->createView(),
]);
}
}
Le second paramètre de createForm() est facultatif est sert à préciser des valeurs initiales dans le formulaire qui seront injectées en Twig, mais elles peuvent aussi l'être via le fichier du formulaire dans les paramètres de chaque champ.
Traitement post-validation
modifierDans la même méthode du contrôleur qui injecte le formulaire, il faut prévoir le traitement post-validation. Par exemple pour mettre à jour l'entité en base :
if (empty($myEntity)) {
$myEntity = new MyEntity();
}
$form = $this->createForm(MyEntityType::class, $myEntity);
$form->handleRequest($request); // Cette méthode remplit l'objet avec les valeurs postées dans $request pour les champs du formulaires mappés
if ($form->isSubmitted() && $form->isValid()) {
// Mise à jour d'un champ non mappé (ex : car absent de $myEntity)
$email = $form->get('email')->getData();
$this->em->persist($email);
$this->em->flush();
return $this->redirectToRoute('home');
}
Fichier du formulaire
modifierDans SF4, l'espace de nom Symfony\Component\Form\Extension\Core\Type propose 35 types de champ, tels que :
- Text
- TextArea
- Email (avec validation en option de la présence d'arrobase ou de domaine)
- Number
- Date
- Choice (menu déroulant)
- Checkbox (cases à cocher et boutons radio)
- Hidden (caché)
- Submit (bouton de validation).
TextType
modifierExemple[3] :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email', TextType::class, [
'required' => true,
'empty_data' => 'valeur par défaut si vide à la validation',
'data' => 'valeur par défaut préremplie à la création',
'constraints' => [new Assert\NotBlank()],
'attr' => ['class' => 'ma_classe_CSS'],
]);
}
Pour préremplir des valeurs dans les champs :
$form->get('email')->setData($user->getEmail());
L'attribut "required" peut être interprété par les navigateurs comme un "NotBlank", mais il faut tout de même le compléter avec la contrainte sans quoi un simple retrait du "required" de la page web par la console du navigateur pourrait contourner l'obligation.
NumberType
modifierCette classe génère une balise input type="number"
, qui empêche donc les navigateurs d'écrire des lettres dedans en HTML5.
D'autre part, il y a aussi les problématiques des nombres minimum et maximum, et des séparateurs décimaux et de milliers.
Ex :
$builder ->add('email', NumberType::class, [ 'html5' => true, 'constraints' => [new Assert\Positive()], 'attr' => [ 'onkeypress' => 'return (event.charCode > 47 && event.charCode < 58) || event.charCode == 44 || event.charCode == 45', ], ]);
ChoiceType
modifierIl faut injecter le tableau des choix du menu déroulant dans la clé "choices", avec en clé ce qui sera visible dans la liste et en valeur ce qui sera envoyé à la soumission[4].
Ex :
$builder ->add('civility', ChoiceType::class, [ 'choices' => ['Choisir' => null, 'M.' => 'M.', 'Mme' => 'Mme'], ])
Dans le cas où une valeur par défaut est définie dans 'data', elle doit appartenir aux valeurs du tableau de "choices", sans quoi elle ne sera pas prise en compte.
Si une valeur absente de la liste des choix est envoyée à la soumission, on peut la faire accepter en l'ajoutant à la volée avec[5] :
->addEventListener(FormEvents::PRE_SUBMIT, function(FormEvent $event) { ... })
EntityType
modifierDe plus, en installant Doctrine, il est possible d'ajouter un type de champ "entité" directement relié avec un champ de base de données[6].
Ex :
$builder->add('company', EntityType::class, ['class' => Company::class]);
En SF4, il n'y avait pas encore les types CheckboxType ou RadioType : il fallait jouer sur deux paramètres de EntityType
ainsi :
Élément | Expanded | Multiple |
---|---|---|
Sélecteur | false | false |
Sélecteur multiple | false | true |
Boutons radio | true | false |
Cases à cocher | true | true |
Exemple :
$builder->add('gender', EntityType::class, ['expanded' => true, 'multiple' => false]);
Pour lui donner une valeur par défaut, il faut lui injecter un objet :
$builder->add('company', EntityType::class, [ 'class' => Company::class, 'choice_label' => 'name', 'data' => $company, ]);
Sous-formulaire
modifierUtiliser le nom du sous-formulaire comme type :
$builder->add('company', MySubformType::class, [ 'label' => false, ]);
Validation
modifierValidation depuis les entités
modifierLe validateur de formulaire d'entité peut utiliser les annotations des entités. Ex :
use Symfony\Component\Validator\Constraints as Assert;
...
#[Assert\Type('string')]
#[Assert\NotBlank]
#[Assert\Length(
min: 1,
max: 255,
)]
En PHP < 8 :
use Symfony\Component\Validator\Constraints as Assert;
...
/**
* @Assert\Type("string")
* @Assert\NotBlank
* @Assert\Length(
* min = 2,
* max = 50
* )
*/
Plusieurs types de données sont déjà définis, comme l'email ou l'URL[7]. Ex :
@Assert\Email()
Validation depuis les formulaires
modifierSinon il permet aussi des contrôles plus personnalisés dans les types (qui étendent Symfony\Component\Form\AbstractType). Ex :
'constraints' => [
new Assert\NotBlank(),
new GreaterThanOrEqual(2),
new Assert\Callback([ProductChecker::class, 'check']),
],
Validation avec un service
modifierPour valider une entité depuis le service validateur[8] :
use Symfony\Component\Validator\Validator\ValidatorInterface; ... $validator->validate( $entity, $entityConstraint );
NB : le second paramètre est optionnel.
Bien que l'on voit des services correspondant aux contraintes du validateur, on ne peut pas les injecter comme les autres services mais uniquement les utiliser via le validateur général.
Exemple pour valider un email :
php bin/console debug:container |grep -i validator |grep -i email validator.email Symfony\Component\Validator\Constraints\EmailValidator
use Symfony\Component\Validator\Constraints\Email;
use Symfony\Component\Validator\Validator\ValidatorInterface;
...
$this->validator->validate(
'mon_email@example.com',
new Email()
);
Appel du formulaire Symfony dans la vue
modifierLes fonctions Twig permettant d'ajouter les éléments du formulaire sont :
- form_start
- form_errors
- form_row
- form_widget
- form_label
Pour afficher tout le formulaire, dans l'ordre où les champs ont été définis en PHP :
{{ form_start(form) }}
{{ form_end(form) }}
Pour n'afficher qu'un seul champ :
{{ form_widget(form.choosen_credit_card) }}
Les mêmes attributs qu'en PHP peuvent être définis en paramètre. Ex :
{{ form_widget(form.name, {'attr': {'class': 'address', 'placeholder': 'Entrer une adresse'} }) }}
{{ form_label(form.name, null, {'label_attr': {'class': 'address'}}) }}
Exemple complet :
{{ form_start(form) }}
{{ form_errors(form) }}
{{ form_label(form.name, 'Label du champ "name" écrasé ici') }}
{{ form_row(form.name) }}
{{ form_widget(form.message, {'attr': {'placeholder': 'Remplacez ce texte par votre message'} }) }}
{{ form_rest(form) }}
{{ form_row(form.submit, { 'label': 'Submit me' }) }}
{{ form_end(form) }}
Références
modifier- ↑ https://symfony.com/doc/current/components/http_foundation.html
- ↑ https://symfony.com/doc/current/forms.html#form-validation
- ↑ https://symfony.com/doc/current/reference/forms/types/form.html#empty-data
- ↑ https://symfony.com/doc/current/reference/forms/types/choice.html
- ↑ https://github.com/symfony/symfony/issues/42451
- ↑ https://symfony.com/doc/master/reference/forms/types/entity.html
- ↑ https://symfony.com/doc/current/validation.html#string-constraints
- ↑ https://symfony.com/doc/current/validation.html#using-the-validator-service