Objectifs de cette séance (1h) :
Passer à une seule méthode :
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(),
    ]);
    # }
}
Fusionne en une seule méthode gestion du GET et du POST
Cf. Forms dans la documentation Symfony.
#[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(),
    ]);
}
Symfony peut aussi générer des classes contenant le code des contrôleurs et les formulaires
symfony console make:crud
#[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);
    // Gestion du POST
    if ($form->isSubmitted() && $form->isValid()) {
        $todoRepository->add($todo, true);
        return $this->redirectToRoute('todo_index', [], Response::HTTP_SEE_OTHER);
    }
    // Gestion du GET ou problème validation
    return $this->renderForm('todo/new.html.twig', [
        'todo' => $todo,
        'form' => $form,
    ]);
}
TodoType est vue plus loin
Gabarit todo/new.html.twig :
{% extends 'base.html.twig' %}
{% block title %}New Todo{% endblock %}
{% block main %}
    <h1>Create new Todo</h1>
    {{ include('todo/_form.html.twig') }}
    <a href="{{ path('todo_list') }}">back to list</a>
{% endblock %}
Gabarit
templates/todo/_form.html.twig :
{{ form_start(form) }}
    {{ form_widget(form) }}
    <button class="btn">{{ button_label|default('Save') }}</button>
{{ form_end(form) }}
(aussi inclus par todo/edit.html.twig)
TodoType gestionnaire de formulaireclass 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,
        ]);
    }
}
Mécanisme MapEntity des contrôleurs
{id} de l’attribut Route : extrait du chemin de la requête HTTP$todo, instance de TodoTodo (appel à
TodoRepository::find() automagique)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,
        ));
    }
Objectifs de cette séance (1h) :
Création d’une Todo dans le contexte d’un Project
Version /todo/new créée via make:crud
Formulaire TodoType :
class TodoType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            // ...
            ->add('project');
la référence au projet de la tâche (Todo::project) est gérée comme
une propriété « ordinaire » de la tâche dans le formulaire
Résultat : liste de choix parmi les projets
Pas ce qu’on souhaite, si une tâche ne peut pas changer de projet
project_show, sur /project/{id})/project/{id}/addtodoid de projet (connu) !#[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 :
{id} de la routetâche créée dans son projet :
$todo->setProject($project)
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'] ]);
    }
Objectifs de cette séance (1h) :
POST /paste/new HTTP/1.1 Host: example.com Content-Type: multipart/form-data; boundary=---------------------------1493984614149677742994700663 Content-Length: 1013137 -----------------------------1493984614149677742994700663 Content-Disposition: form-data; name="paste[content]" Joulis -----------------------------1493984614149677742994700663 Content-Disposition: form-data; name="paste[imageFile][file]"; filename="joulis.png" Content-Type: image/png 89504e470d0a1a0a0000000d49484452000003ef000003500802000000d6 84c9d20000000373424954080808dbe14fe00000001974455874536f6674 7761726500676e6f6d652d73637265656e73686f74ef03bf3e0000200049 444154789cacbddb92f3489226f6b97b440024f3f49faaaa0fd333bd6b2b e95ab7b27d1999ccf4b27a00ad64265977cf6cef7457fd87cc240920c2dd 75118120c8fcab6d2f848b4c12040271f0e3e71e0efae31fffe8eecc0c80 88fe873ffecfcc0c220070777783c13de7cccc2272f9898d880227df1cb5 1100214562e4bc48e2a2cb3c2fbbfdf89ffff3fff2bfffafffdbbb770f0e 64982948782986c00e14c000000410e06887030a38dc9cb09e37adffe006 38dc1d4a00dc6005aa0a804988e00a33a742eeee4eb5930048090c765b07 ea06ad9f01981a0017eb67ea35446e6666d61aa1d6a0885cee3533337737 b37ac1767e4c0840ce595555b54e3e337b7d84998830b3aa1e4fc7799ee1 1c63bc74d2cc8d6afb00587ac7a8ce7c3ddf6680b9b656cf9b1933b74636 5d0220ad016a73be36d8afec2b6ba4ee9e735ed707ed4e0611c1c9ddad0d c500b85d7ad5db717737727714ab679899888cbc4e0b33d7079b50049b50 7052e218060e29c6c8297064621611171e778999534a87fdb8dbedd22024 8d7a94200e1308c10402982031dc51668401309419d3abfdebbffe2b8fe9 a7dffc187794cb3c1cc22e88c2046ce60202371a3586aae5a594a9308518 024706508a91331138801444200013b4a8bb47b0bb0f7b91082b30852be6 d37c3a9dbe7dfb0620c6e8ee9ffffd97bffce52fcfcfcf089e4f131179d1
Exemple : utilisation de VichUploaderBundle pour mettre des photos
dans les Pastes
public/
Ajout propriété à entité Doctrine (décorée par annotation
Vich\Uploadable)
#[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;
                  //...
          }
}
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 ?'
                              ])
                ;
        }
{% extends 'base.html.twig' %}
{% block title %}Paste{% endblock %}
{% block main %}
    <h1>Paste</h1>
    <table class="table">
        <tbody>
            <tr>
                <th>Id</th>
                <td>{{ paste.id }}</td>
            </tr>
            ...
            <tr>
                    <th>Image</th>
                    <td>
                       <img src="{{ vich_uploader_asset(paste, 'imageFile') }}"/>
                    </td>
            </tr>
        </tbody>
    </table>
    <a href="{{ path('app_paste_index') }}">back to list</a>
{% endblock %}
Fichier déposé : public/images/pastes/joulis-68ac7be5bf930013195640.png
make:crud