Aller au contenu

Programmation PHP avec Symfony/Formulaire

Un livre de Wikilivres.

Le 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.

Terminal

Logo

 composer require symfony/form


Les formulaires présents sont ensuite listables avec :

Terminal

Logo

 bin/console debug:form


Et vérifiables individuellement :

Terminal

Logo

 bin/console debug:form "App\Service\Form\MyForm"


Avec le composant maker, on peut créer un formulaire pour chaque entité Doctrine à modifier :

Terminal

Logo

 composer require symfony/maker-bundle
 bin/console make:form


Pour ajouter des contrôles sur les champs, il existe un deuxième composant Symfony[2] :

Terminal

Logo

 composer require symfony/validator


Injection du formulaire dans un Twig

[modifier | modifier le wikicode]
class 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

[modifier | modifier le wikicode]

Dans 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

[modifier | modifier le wikicode]

Dans 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).

Exemple[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());

Logo

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.

Cette 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',
                ],
        ]);

Il 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) {
                ...
            })

De 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]);

Logo

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

[modifier | modifier le wikicode]

Utiliser le nom du sous-formulaire comme type :

$builder->add('company', MySubformType::class, [
    'label' => false,
]);

Validation depuis les entités

[modifier | modifier le wikicode]

Le 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

[modifier | modifier le wikicode]

Sinon 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

[modifier | modifier le wikicode]

Pour 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.

Logo

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

[modifier | modifier le wikicode]

Les 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) }}