Architecture(s) et
application(s) Web

CSC4101 - Gestion avancée des routes et formulaires

28/08/2023

Plan de la séquence

Objectifs de cette séance (1h) :

  1. Formulaires améliorés
  2. Aller au-delà des CRUDs basiques
  3. Téléversement d'images

Gestion de formulaire

  • Affichage d’un formulaire HTML : méthode d’un contrôleur (insertion d’un formulaire dans un template)
  • Gestion de la soumission du formulaire : méthode du même contrôleur (par exemple la même méthode)

Cf. Forms dans la documentation Symfony.

Exemple : nouvelle tâche

#[Route('/new', name: 'todo_new', methods: ['GET', 'POST'])]
public function new(Request $request, TodoRepository $todoRepository): Response
{
        $todo = new Todo();
        $form = $this->createForm(TodoType::class, $todo);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
                $todoRepository->add($todo, true);

                return $this->redirectToRoute('todo_index', [], Response::HTTP_SEE_OTHER);
        }

        return $this->renderForm('todo/new.html.twig', [
                'todo' => $todo,
                'form' => $form,
        ]);
}

Gabarit templates/todo/_form.html.twig (inclus par todo/new.html.twig) :

{% form_theme form 'bootstrap_5_layout.html.twig' %}
{{ form_start(form) }}
    {{ form_widget(form) }}
    <button class="btn">{{ button_label|default('Save') }}</button>
{{ form_end(form) }}

Classe gestionnaire de formulaire

Gestion du « Type » Todo

class TodoType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('title', TextType::class,
                  ['required' => false])
            ->add('completed', ChoiceType::class,
                  ['choices' => [
                      'Maybe' => null,
                      'Yes' => true,
                      'No' => false ]
                  ])
            ->add('project');
    }
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => Todo::class,
        ]);
    }
}

Chargement auto des instances

Mécanisme MapEntity des contrôleurs

  • Paramètre {id} de l’attribut Route extrait de requête HTTP
  • Argument de la méthode instance de Todo
  • Chargement auto grâce au Repository (TodoRepository::find())
class TodoController extends Controller
{
    #[Route('/todo/{id}', name: 'todo_show'),
      requirements: ['id' => '\d+'], methods: ['GET'])]
    public function showAction(Todo $todo): Response
    {
        return $this->render('todo/show.html.twig', array(
            'todo' => $todo,
        ));
    }

Gestion des formulaires en une méthode

  • Rendre le code plus compact
  • Lisibilité ?

Rappel : Variante à 2 méthodes

Méthode gérant le GET : affichage du formulaire HTML

#[Route('/todo/new', name: 'todo_new_get', methods: ['GET'])]
public function newFormGet(): Response
{
    $todo = new Todo();
    // init valeurs par défaut
    $form = $this->createForm(TodoType::class, $todo);

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

Méthode gérant le POST : traitement des données soumises

#[Route('/todo/new', name: 'todo_new_post', methods: ['POST'])]
public function newFormPost(Request $request, TodoRepository $todoRepository): Response
{
    $todo = new Todo();
    $form = $this->createForm(TodoType::class, $todo);
    $form->handleRequest($request);

    if($form->isSubmitted() && $form->isValid()) {
        $todoRepository->add($todo, true);

        return $this->redirectToRoute('todo_index');
    }
    # else {
    return $this->render('todo/new.html.twig', [
        'todo' => $todo,
        'form' => $form->createView(),
    ]);
    # }
}

Fusionner en une seule méthode

#[Route('/todo/new', name: 'todo_new', methods: ['GET', 'POST'])]
public function new(Request $request, TodoRepository $todoRepository): Response
{
    $todo = new Todo();
    $form = $this->createForm(TodoType::class, $todo);
    $form->handleRequest($request);
    if ($form->isSubmitted() && $form->isValid()) {
        $todoRepository->add($todo, true);

        return $this->redirectToRoute('todo_index');
    }

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

Génération des contrôleurs et formulaires

Symfony peut aussi générer des classes contenant le code des contrôleurs et les formulaires

symfony console make:crud

Plan de la séquence

Objectifs de cette séance (1h) :

  1. Formulaires améliorés
  2. Aller au-delà des CRUDs basiques
  3. Téléversement d'images

Ajout entités liées

Création d’une Todo dans le contexte d’un Project

Version basique

Version /todo/new créée via make:crud

  • Formulaire : le projet est une propriété de la tâche (Todo::project)

    class TodoType extends AbstractType
    {
        public function buildForm(FormBuilderInterface $builder, array $options)
        {
            $builder
                // ...
                ->add('project');
    
    

Résultat : liste de choix parmi les projets

ajout-popup-entite-liee.png

Figure 1 : « Sélection d’une entité liée dans un popup »

Pas ce qu’on souhaite si une tâche ne peut pas changer de projet

Accès depuis un projet

  • Sous la liste des tâches d’un projet, bouton/lien « ajouter nouveau »
  • Route : /project/{id}/addtodo
  • id de projet (connu)

Code final

#[Route('/project/{id}/addtodo', name: 'todo_add',
        methods: ['GET', 'POST'])]
public function addTodo(Request $request, Project $project,
                        TodoRepository $todoRepository): Response
{
        $todo = new Todo();
        $todo->setProject($project);

        $form = $this->createForm(TodoType::class, $todo);
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {

            $todoRepository->add($todo, true);
            // ...

Contexte : tâche créée dans son projet $todo->setProject($project)

Supprimer le champ project du formulaire TodoType

Éviter la modification du champ project.

Comportement optionnel (garde fonctionnement initial formulaire nouvelle tâche isolée).

$form = $this->createForm(TodoType::class, $todo,
        ['display_project' => false]);

public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('title')
            ->add('created', DateType::class, [
                  'widget' => 'single_text'])
            ->add('project', null, [
                  'disabled' => $options['display_project'] ]);
    }

</formulaires>

Plan de la séquence

Objectifs de cette séance (1h) :

  1. Formulaires améliorés
  2. Aller au-delà des CRUDs basiques
  3. Téléversement d'images

Outil pour upload d’image dans formulaire Symfony

Exemple : utilisation de VichUploaderBundle pour mettre des photos dans les Pastes

  • Formulaire upload de photo
  • Stockage dans public/
  • Affichage dans gabarit

Entité Paste

#[ORM\Entity(repositoryClass: PasteRepository::class)]
#[Vich\Uploadable]
class Paste
{
    //...
    #[ORM\Column(nullable: true)]
    private ?string $imageName = null;

    #[Vich\UploadableField(mapping: 'pastes',
                           fileNameProperty: 'imageName')]
    private $imageFile;

    //           .../...
  • imageName stocké en base (Doctrine)
  • imageFile objet « fichier téléversé » Symfony
        /**
         * @param File|\Symfony\Component\HttpFoundation\File\UploadedFile $imageFile
         */
        public function setImageFile(?File $imageFile = null): void
        {
                $this->imageFile = $imageFile;

                //...
        }

}

Formulaire Paste

class PasteType extends AbstractType
{
        public function buildForm(FormBuilderInterface $builder,
                              array $options)
        {
                $builder
                        ->add('content')
                        ->add('created')
                        ->add('content_type')
                        ->add('imageName', TextType::class,
                              ['disabled' => true])
                        ->add('imageFile', VichImageType::class,
                              [
                                'required' => false,
                                'delete_label' => 'Delete image ?'
                              ])
                ;
        }

</upload_images>

Take Away

  • Gestion optimale intégrée entre formulaires et Doctrine
  • Ne pas se limiter à make:crud
  • Gestion des données liées dans les associations
  • Bundle pour téléversement d’images simple

Copyright

  • Document propriété de ses auteurs et de Télécom SudParis (sauf exceptions explicitement mentionnées).
  • Réservé à l’utilisation pour la formation initiale à Télécom SudParis.