Si vous ne l’avez pas fait, vous êtes invités à lire la première partie de la série qui introduit quelques points repris dans cet article.

Petit rappel : 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.

Le contrôleur d’édition

<?php

/**
 * @Route("/{id}/edit", name="article_edit", methods={"GET", "PUT"})
 */
public function editAction(Request $request, Article $article)
{
    $form = $this->createForm(ArticleType::class, $article, [
        'method' => Request::METHOD_PUT,
    ])
        ->add('submit', SubmitType::class, ['label' => 'Update'])
    ;

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

            $this->addFlash(
                'success',
                sprintf(
                    'The article "%s" has been updated.',
                    $article->getTitle()
                )
            );
            
            return $this->redirectToRoute('article_list');
        }
        
        $this->addFlash('danger', 'Please fix the errors.');
    }

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

Le verbe HTTP

La norme HTTP indique que dans le cas de la mise à jour totale d’une ressource, il faut utiliser la méthode PUT. La norme HTML indique que seules les méthodes GET et POST sont valides pour un formulaire HTML.

Problème, comment satisfaire les deux normes ?

Symfony sait traiter cette problématique en “corrigeant” la méthode de la requête. Cette fonctionnalité est assurée par un champ caché _method qui contiendra la méthode souhaitée alors que la méthode de la balise <form> sera POST.

Dans notre cas, nous autorisons la méthode PUT dans la description de la route, ainsi que dans les options du formulaire.

Le convertisseur de paramètres (ParamConverter)

Nous avons choisi de passer l’identifiant de notre entité dans l’URL. Mais reste à charger la bonne entité à transmettre au formulaire. Ceci pour qu’il puisse en tirer les valeurs actuelles des champs, mais également mettre à jour les données à la soumission du formulaire.

Étant donné qu’on retrouve la clé primaire de notre entité dans l’URL, nous sommes dans un cas simple. Il nous suffit donc de mettre un typehint Article à l’argument du contrôleur. Automatiquement Symfony va en déduire que la classe est une entité et qu’il faut utiliser le convertisseur de paramètres. Ensuite le rapprochement va être fait entre les noms de paramètres d’URL et les noms des champs de l’entité pour pouvoir en sélectionner une.

Pratique : si aucune entité correspondant aux paramètres d’URL n’est trouvée, alors une erreur 404 est retournée.

Le convertisseur de paramètres est un moyen pratique de faire moins de code dans le contrôleur. Et qui dit moins de code, dit moins de risques, de problèmes et de tests à écrire.

Dans les cas plus complexes, par exemple si une entité a des relations vers d’autres entités ou que l’URL a des paramètres qu’il faut utiliser d’une manière spécifique, vous pouvez lui spécifier la méthode du repository qui va se charger d’effectuer la requête :

<?php

/**
 * @Route("/{id}/edit", name="article_edit", methods={"GET", "PUT"})
 * @ParamConverter("article", class="AppBundle:Article", options={"repository_method"="findByIdWithJoins"})
 */
public function editAction(Request $request, Article $article)

Le form

Pas de $form->getData() dans ce contrôleur. Quand la méthode handleRequest() est appelée, les modifications sont alors appliquées à l’entité. Le contrôleur possède déjà le pointeur de l’entité, c’est le même qui est passé en argument de la création du formulaire. Il n’est donc pas utile de récupérer à nouveau ce pointeur.

Doctrine et son manager

Nous n’avons pas fait de $om->persist($article);.

Effectuer le persist() n’est nécessaire que dans le cas où l’entité n’est pas managée par Doctrine, comme c’est le cas après un new Article() par exemple.

Ici, le convertisseur de paramètre a fait appel à Doctrine pour charger l’entité. Une fois chargée de la base de données, l’entité est placée dans le manager. La seule chose à faire est donc flush(). Les modifications seront alors poussées en base de données.

Lors du flush Doctrine inspecte toutes les entités managées pour en ressortir tous les changements et toutes les mises à jour à effectuer. Dans notre cas, Doctrine n’a qu’une entité, mais attention aux fonctionnalités où vous chargez beaucoup d’entités !

Le template

Grâce au travail effectué à la création, le template d’édition reste très simple.

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

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

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

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

La série