Qui dit données, dit entités et formulaires. Afin de permettre à nos utilisateurs de saisir leurs informations, nous mettons en place des CRUD (Create, Read, Update, Delete) permettant la gestion des données.

Cet article est le premier d’une série d’extraits de cas pratique sur les formulaires.

Le sujet de l’internationalisation ne sera pas abordé dans les exemples. Mais il est recommandé d’utiliser des clés de traduction pour les labels des widgets ainsi que pour les messages qui seront affichés à l’utilisateur.

Commençons donc avec un exemple basique pour la création.

L’entité et le FormType

Nous allons utiliser une entité Doctrine très simple…

<?php

/**
 * @ORM\Entity
 */
class Article
{
    /**
     * @var int
     *
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @var string
     *
     * @ORM\Column
     *
     * @Assert\Length(max="50")
     */
    protected $title;

    /**
     * @var string
     *
     * @ORM\Column(type="text")
     */
    protected $content;
    
    // ... Getter / Setters ...
}

… ainsi qu’un FormType (à placer dans le dossier Form\Type) correspondant à cette classe.

<?php

class ArticleType extends AbstractType
{
    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('title', TextType::class)
            ->add('content', TextareaType::class)
        ;
    }

    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver
            ->setDefaults([
                'data_class' => Article::class,
            ])
        ;
    }
}

On peut remarquer trois points :

  • L’option data_class qui définit le type de données attendues, soit une instance de l’entité Article
  • Pas de champ hidden “id” qui contiendrait la clef primaire de l’entité
  • Pas de boutons

Pour le champ “id”, qui contiendrait la clef primaire, il n’est tout simplement pas nécessaire dans le FormType. Dans le cadre de la création, la clef primaire est auto-genérée par Doctrine. Dans le cadre de l’édition, la clef primaire qui permettra de sélectionner l’entité à éditer sera transmise par un autre moyen qu’un champ hidden, comme l’URL par exemple.

Placer les boutons directement dans le FormType empêche l’imbrication du formulaire en tant que champs d’un formulaire parent. Imaginez que vous ayez une autre entité “dossier” dont le formulaire créera en même temps un ou plusieurs articles, si des boutons se trouvent dans la classe ArticleType le formulaire du dossier aura alors de multiples boutons.

Le contrôleur de création

<?php

/**
 * @Route("/new", name="article_new", methods={"GET", "POST"})
 */
public function newAction(Request $request)
{
    $form = $this->createForm(ArticleType::class)
        ->add('submit', SubmitType::class, ['label' => 'Create', 'attr' => ['class' => 'btn-primary pull-right']])
    ;

    if ($form->handleRequest($request)->isSubmitted()) {
        if ($form->isValid()) {
            $om = $this->getDoctrine()->getManager();

            $article = $form->getData();
            $om->persist($article);
            $om->flush();

            $this->addFlash(
                'success',
                sprintf(
                    'The article "%s" has been created.',
                    $article->getTitle()
                )
            );

            return $this->redirectToRoute('article_edit', ['id' => $article->getId()]);
        }

        $this->addFlash('danger', 'Please fix the errors.');
    }

    return $this->render('@App/Article/new.html.twig', [
        'form' => $form->createView(),
    ]);
}

Dans l’optique d’avoir des vues les plus simples possible avec des formulaires présentés de manière identique (comme une administration par exemple), il faut utiliser la méthode form(form) qui va s’occuper d’afficher tous les widgets.

Les boutons sont souvent ajoutés dans la vue, ce qui empêche l’utilisation de form(form), ou dans le FormType, ce qui empêche d’ajouter le formulaire à un autre. Toutefois, pour éviter ces pratiques, il est possible d’ajouter le bouton dans le contrôleur.

De même, la méthode du formulaire et son action sont souvent placées dans la vue. Symfony permet d’obtenir les méthodes HTTP “corrigées” des requêtes et la fonction handleRequest() vérifie si la méthode est correcte. Dans le cas présent, la méthode POST est la méthode par défaut et ne nécessite donc pas d’être redéfinie.

L’option data_class peut être devinée par le formulaire, nous pourrions donc omettre de la définir, seulement si une donnée initiale lui est passé à sa création. Ici en étant explicite, le formulaire sait qu’il doit utiliser une instance de cette classe pour mapper l’array de données. Il appellera ainsi new Article() avant d’appeler les setters pendant la soumission si aucune valeur n’existe. C’est une bonne pratique de laisser le formulaire construire l’entité, le formulaire devient la “factory” évitant un new dans le controller. À noter que l’on peut toujours utiliser l’option empty_data lorsque l’entité a besoin d’injection dans le constructeur ; avec une simple callable qui prend en unique argument le formulaire soumis. Vous pouvez en savoir plus avec cet article de la documentation.

Les templates

Finalement, ils sont très simple.

Fichier correspondant à @App/Article/new.html.twig:

{% extends '@App/layout.html.twig' %}

{% block body %}
    <h1>Create an article</h1>

    {% include '@App/Article/_form.html.twig' %}
{% endblock %}

Fichier correspondant à @App/Article/_form.html.twig:

{{ form(form) }}

L’inclusion du template de rendu du formulaire permettra d’avoir un seul affichage pour la création et l’édition. Pratique en cas de personnalisation du formulaire que l’on souhaiterait faire apparaître dans les deux cas.

La série