Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

[FrameworkBundle] Add renderForm() helper setting the appropriate HTTP status code#39843

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
fabpot merged 1 commit intosymfony:5.xfromdunglas:feat/renderForm
Jan 17, 2021

Conversation

@dunglas
Copy link
Member

@dunglasdunglas commentedJan 14, 2021
edited
Loading

QA
Branch?5.x
Bug fix?no
New feature?yes
Deprecations?no
Ticketsn/a
LicenseMIT
Doc PRtodo

A 422 HTTP status code should be returned after the submission of an invalid form. Some libraries includingTurbo rely on this behavior and will not display the updated form (containing errors) unless this status code is present.

Rails alsorecently switched to this behavior by default for the same reason.

I propose to introduce a new helper method rendering the form and setting the appropriate status code. It makes the code cleaner:

// src/Controller/TaskController.php// ...useSymfony\Component\HttpFoundation\Request;class TaskControllerextends AbstractController{publicfunctionnew(Request$request):Response    {$task =newTask();$form =$this->createForm(TaskType::class,$task);$form->handleRequest($request);if ($form->isSubmitted() &&$form->isValid()) {$task =$form->getData();// ...return$this->redirectToRoute('task_success');        }return$this->renderForm('task/new.html.twig',$form);    }}

aleksblendwerk reacted with thumbs up emoji
@chalasrchalasr added this to the5.x milestoneJan 14, 2021
@fabpot
Copy link
Member

Thank you@dunglas.

@lecneri
Copy link

What if I have 2+ forms?
taking a hook in@fabpot ' solution I suggest this (obs, didn't make a PR because I'm not sure how to create a proper test):

/**  * Renders a collection of forms.  *  * An array of forms is passed to the template using the key as form name.  * If any form is invalid, a 422 status code is returned.  */publicfunctionrenderFormCollection(string$view,array$forms,array$parameters = [],Response$response =null):Response{$formCollection = [];foreach ($formsas$formName =>$form) {if ($form->isSubmitted() && !$form->isValid()) {if ($response ===null) {$response =newResponse();            }$response->setStatusCode(Response::HTTP_UNPROCESSABLE_ENTITY);        }$formCollection[$formName] =$form->createView();    }return$this->render($view,array_merge($formCollection,$parameters),$response);}

nicolas-grekas added a commit that referenced this pull requestApr 16, 2021
… helper (dunglas)This PR was squashed before being merged into the 5.3-dev branch.Discussion----------[FrameworkBundle] Add AbstractController::handleForm() helper| Q             | A| ------------- | ---| Branch?       | 5.x| Bug fix?      | no| New feature?  | yes <!-- please update src/**/CHANGELOG.md files -->| Deprecations? | no <!-- please update UPGRADE-*.md and src/**/CHANGELOG.md files -->| Tickets       | n/a| License       | MIT| Doc PR        |symfony/symfony-docs#15217Some libraries such as Turbo require to strictly follow the HTTP specification (and especially to use proper status codes) to deal with forms.In#39843, I introduced a new `renderForm()` helper for this purpose. But I'm not very satisfied by it. The current approach has several problems:1. It calls `$form->isValid()` two times, which may hurt performance2. It sets the proper status code in case of validation error (422), but not for the redirection when the entity is created or updated (306). The user must do this manually (and so be aware of these HTTP subtleties).3. It hides the verbosity of the Form component a bit, but I'm a sure that we can reduce it moreThis PR proposes an alternative helper, `handleForm()`, which handles automatically 80% of the use cases, provide an extension point for the other 20%, and can also serve as a quick example for users to handle form in a custom way (by control-clicking on the function to see the code and copy/paste/adapt it).* if the form is not submitted, the Twig template passed in $view is rendered and a 200 HTTP status code is set* if the form is submitted but invalid, the Twig template passed in $view is rendered and 422 HTTP status code is set* if the form is submitted and valid, the entity is saved (only if it is managed by Doctrine ORM), a 306 HTTP status code is set and the Location HTTP header is set to the value of $redirectUrlBefore (standard case):```php    #[Route('/{id}/edit', name: 'conference_edit', methods: ['GET', 'POST'])]    public function edit(Request $request, Conference $conference): Response    {        $form = $this->createForm(ConferenceType::class, $conference);        $form->handleRequest($request);        $submitted = $form->isSubmitted();        $valid = $submitted && $form->isValid();        if ($valid) {            $this->getDoctrine()->getManager()->flush();            return $this->redirectToRoute('conference_index', [], Response::HTTP_SEE_OTHER);        }        $response = $this->render('conference/edit.html.twig', [            'conference' => $conference,            'form' => $form->createView(),        ]);        if ($submitted && !$valid) {            $response->setStatusCode(Response::HTTP_UNPROCESSABLE_ENTITY);        }        return $response;    }```With the new helper:```php    #[Route('/{id}/edit', name: 'conference_edit', methods: ['GET', 'POST'])]    public function edit(Request $request, Conference $conference): Response    {        $form = $this->createForm(ConferenceType::class, $conference);        return $this->handleForm(            $request,            $form,            view: 'conference/edit.html.twig',            redirectUrl: $this->generateUrl('conference_index')        );    }```Before (more advanced use case):```php    #[Route('/{id}/edit', name: 'conference_edit', methods: ['GET', 'POST'])]    public function edit(Request $request, Conference $conference, HubInterface $hub): Response    {        $form = $this->createForm(ConferenceType::class, $conference);        $form->handleRequest($request);        $submitted = $form->isSubmitted();        $valid = $submitted && $form->isValid();        if ($valid) {            $this->getDoctrine()->getManager()->flush();            $hub->publish(                new Update(                    'conference:'.$conference->getId(),                    $this->renderView('conference/edit.stream.html.twig', ['conference' => $conference])                )            );            return $this->redirectToRoute('conference_index', [], Response::HTTP_SEE_OTHER);        }        $response = $this->render('conference/edit.html.twig', [            'conference' => $conference,            'form' => $form->createView(),        ]);        if ($submitted && !$valid) {            $response->setStatusCode(Response::HTTP_UNPROCESSABLE_ENTITY);        }        return $response;    }```With the new helper (more advanced case):```php    #[Route('/{id}/edit', name: 'conference_edit', methods: ['GET', 'POST'])]    public function edit(Request $request, Conference $conference, HubInterface $hub): Response    {        $form = $this->createForm(ConferenceType::class, $conference);        $response = $this->handleForm(            $request,            $form,            view: 'conference/edit.html.twig',            redirectUrl: $this->generateUrl('conference_index')        );        if ($response->isRedirection()) {            $hub->publish(                new Update(                    'conference:' . $conference->getId(),                    $this->renderView('conference/edit.stream.html.twig', ['conference' => $conference])                )            );        }        return $response;    }```This also works without named parameters. I also considered passing a callback to be executed on success, but I'm happier with the current solution.WDYT?TODO:* [x] update testsCommits-------5228546 [FrameworkBundle] Add AbstractController::handleForm() helper
symfony-splitter pushed a commit to symfony/framework-bundle that referenced this pull requestApr 16, 2021
… helper (dunglas)This PR was squashed before being merged into the 5.3-dev branch.Discussion----------[FrameworkBundle] Add AbstractController::handleForm() helper| Q             | A| ------------- | ---| Branch?       | 5.x| Bug fix?      | no| New feature?  | yes <!-- please update src/**/CHANGELOG.md files -->| Deprecations? | no <!-- please update UPGRADE-*.md and src/**/CHANGELOG.md files -->| Tickets       | n/a| License       | MIT| Doc PR        |symfony/symfony-docs#15217Some libraries such as Turbo require to strictly follow the HTTP specification (and especially to use proper status codes) to deal with forms.Insymfony/symfony#39843, I introduced a new `renderForm()` helper for this purpose. But I'm not very satisfied by it. The current approach has several problems:1. It calls `$form->isValid()` two times, which may hurt performance2. It sets the proper status code in case of validation error (422), but not for the redirection when the entity is created or updated (306). The user must do this manually (and so be aware of these HTTP subtleties).3. It hides the verbosity of the Form component a bit, but I'm a sure that we can reduce it moreThis PR proposes an alternative helper, `handleForm()`, which handles automatically 80% of the use cases, provide an extension point for the other 20%, and can also serve as a quick example for users to handle form in a custom way (by control-clicking on the function to see the code and copy/paste/adapt it).* if the form is not submitted, the Twig template passed in $view is rendered and a 200 HTTP status code is set* if the form is submitted but invalid, the Twig template passed in $view is rendered and 422 HTTP status code is set* if the form is submitted and valid, the entity is saved (only if it is managed by Doctrine ORM), a 306 HTTP status code is set and the Location HTTP header is set to the value of $redirectUrlBefore (standard case):```php    #[Route('/{id}/edit', name: 'conference_edit', methods: ['GET', 'POST'])]    public function edit(Request $request, Conference $conference): Response    {        $form = $this->createForm(ConferenceType::class, $conference);        $form->handleRequest($request);        $submitted = $form->isSubmitted();        $valid = $submitted && $form->isValid();        if ($valid) {            $this->getDoctrine()->getManager()->flush();            return $this->redirectToRoute('conference_index', [], Response::HTTP_SEE_OTHER);        }        $response = $this->render('conference/edit.html.twig', [            'conference' => $conference,            'form' => $form->createView(),        ]);        if ($submitted && !$valid) {            $response->setStatusCode(Response::HTTP_UNPROCESSABLE_ENTITY);        }        return $response;    }```With the new helper:```php    #[Route('/{id}/edit', name: 'conference_edit', methods: ['GET', 'POST'])]    public function edit(Request $request, Conference $conference): Response    {        $form = $this->createForm(ConferenceType::class, $conference);        return $this->handleForm(            $request,            $form,            view: 'conference/edit.html.twig',            redirectUrl: $this->generateUrl('conference_index')        );    }```Before (more advanced use case):```php    #[Route('/{id}/edit', name: 'conference_edit', methods: ['GET', 'POST'])]    public function edit(Request $request, Conference $conference, HubInterface $hub): Response    {        $form = $this->createForm(ConferenceType::class, $conference);        $form->handleRequest($request);        $submitted = $form->isSubmitted();        $valid = $submitted && $form->isValid();        if ($valid) {            $this->getDoctrine()->getManager()->flush();            $hub->publish(                new Update(                    'conference:'.$conference->getId(),                    $this->renderView('conference/edit.stream.html.twig', ['conference' => $conference])                )            );            return $this->redirectToRoute('conference_index', [], Response::HTTP_SEE_OTHER);        }        $response = $this->render('conference/edit.html.twig', [            'conference' => $conference,            'form' => $form->createView(),        ]);        if ($submitted && !$valid) {            $response->setStatusCode(Response::HTTP_UNPROCESSABLE_ENTITY);        }        return $response;    }```With the new helper (more advanced case):```php    #[Route('/{id}/edit', name: 'conference_edit', methods: ['GET', 'POST'])]    public function edit(Request $request, Conference $conference, HubInterface $hub): Response    {        $form = $this->createForm(ConferenceType::class, $conference);        $response = $this->handleForm(            $request,            $form,            view: 'conference/edit.html.twig',            redirectUrl: $this->generateUrl('conference_index')        );        if ($response->isRedirection()) {            $hub->publish(                new Update(                    'conference:' . $conference->getId(),                    $this->renderView('conference/edit.stream.html.twig', ['conference' => $conference])                )            );        }        return $response;    }```This also works without named parameters. I also considered passing a callback to be executed on success, but I'm happier with the current solution.WDYT?TODO:* [x] update testsCommits-------5228546066 [FrameworkBundle] Add AbstractController::handleForm() helper
@fabpotfabpot mentioned this pull requestApr 18, 2021
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

@fabpotfabpotfabpot approved these changes

@weaverryanweaverryanweaverryan approved these changes

@ycerutoycerutoyceruto approved these changes

@chalasrchalasrchalasr approved these changes

Assignees

No one assigned

Projects

None yet

Milestone

5.4

Development

Successfully merging this pull request may close these issues.

7 participants

@dunglas@fabpot@lecneri@weaverryan@yceruto@chalasr@carsonbot

[8]ページ先頭

©2009-2025 Movatter.jp