Skip to content

Commit fa87194

Browse files
committed
feature #39843 [FrameworkBundle] Add renderForm() helper setting the appropriate HTTP status code (dunglas)
This PR was squashed before being merged into the 5.3-dev branch. Discussion ---------- [FrameworkBundle] Add renderForm() helper setting the appropriate HTTP status code | 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 | todo A 422 HTTP status code should be returned after the submission of an invalid form. Some libraries including [Turbo](hotwired/turbo#39) rely on this behavior and will not display the updated form (containing errors) unless this status code is present. Rails also [recently switched to this behavior ](rails/rails#41026) 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: ```php // src/Controller/TaskController.php // ... use Symfony\Component\HttpFoundation\Request; class TaskController extends AbstractController { public function new(Request $request): Response { $task = new Task(); $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); } } ``` Commits ------- 4c77e50 [FrameworkBundle] Add renderForm() helper setting the appropriate HTTP status code
2 parents 93e853d + 4c77e50 commit fa87194

File tree

3 files changed

+70
-3
lines changed

3 files changed

+70
-3
lines changed

src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
5.3
55
---
66

7+
* Added `AbstractController::renderForm()` to render a form and set the appropriate HTTP status code
78
* Added support for configuring PHP error level to log levels
89
* Added the `dispatcher` option to `debug:event-dispatcher`
910
* Added the `event_dispatcher.dispatcher` tag

src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
1818
use Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface;
1919
use Symfony\Component\Form\Extension\Core\Type\FormType;
20+
use Symfony\Component\Form\Form;
2021
use Symfony\Component\Form\FormBuilderInterface;
2122
use Symfony\Component\Form\FormFactoryInterface;
2223
use Symfony\Component\Form\FormInterface;
@@ -289,6 +290,22 @@ protected function stream(string $view, array $parameters = [], StreamedResponse
289290
return $response;
290291
}
291292

293+
/**
294+
* Renders a form.
295+
*
296+
* The FormView instance is passed to the template in a variable named "form".
297+
* If the form is invalid, a 422 status code is returned.
298+
*/
299+
public function renderForm(string $view, FormInterface $form, array $parameters = [], Response $response = null): Response
300+
{
301+
$response = $this->render($view, ['form' => $form->createView()] + $parameters, $response);
302+
if ($form->isSubmitted() && !$form->isValid()) {
303+
$response->setStatusCode(Response::HTTP_UNPROCESSABLE_ENTITY);
304+
}
305+
306+
return $response;
307+
}
308+
292309
/**
293310
* Returns a NotFoundHttpException.
294311
*

src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
1919
use Symfony\Component\Form\Form;
2020
use Symfony\Component\Form\FormConfigInterface;
21+
use Symfony\Component\Form\FormInterface;
22+
use Symfony\Component\Form\FormView;
2123
use Symfony\Component\HttpFoundation\BinaryFileResponse;
2224
use Symfony\Component\HttpFoundation\File\File;
2325
use Symfony\Component\HttpFoundation\JsonResponse;
@@ -31,6 +33,7 @@
3133
use Symfony\Component\Security\Core\User\User;
3234
use Symfony\Component\Serializer\SerializerInterface;
3335
use Symfony\Component\WebLink\Link;
36+
use Twig\Environment;
3437

3538
class AbstractControllerTest extends TestCase
3639
{
@@ -371,7 +374,7 @@ public function testdenyAccessUnlessGranted()
371374

372375
public function testRenderViewTwig()
373376
{
374-
$twig = $this->getMockBuilder(\Twig\Environment::class)->disableOriginalConstructor()->getMock();
377+
$twig = $this->getMockBuilder(Environment::class)->disableOriginalConstructor()->getMock();
375378
$twig->expects($this->once())->method('render')->willReturn('bar');
376379

377380
$container = new Container();
@@ -385,7 +388,7 @@ public function testRenderViewTwig()
385388

386389
public function testRenderTwig()
387390
{
388-
$twig = $this->getMockBuilder(\Twig\Environment::class)->disableOriginalConstructor()->getMock();
391+
$twig = $this->getMockBuilder(Environment::class)->disableOriginalConstructor()->getMock();
389392
$twig->expects($this->once())->method('render')->willReturn('bar');
390393

391394
$container = new Container();
@@ -399,7 +402,7 @@ public function testRenderTwig()
399402

400403
public function testStreamTwig()
401404
{
402-
$twig = $this->getMockBuilder(\Twig\Environment::class)->disableOriginalConstructor()->getMock();
405+
$twig = $this->getMockBuilder(Environment::class)->disableOriginalConstructor()->getMock();
403406

404407
$container = new Container();
405408
$container->set('twig', $twig);
@@ -410,6 +413,52 @@ public function testStreamTwig()
410413
$this->assertInstanceOf(\Symfony\Component\HttpFoundation\StreamedResponse::class, $controller->stream('foo'));
411414
}
412415

416+
public function testRenderFormTwig()
417+
{
418+
$formView = new FormView();
419+
420+
$form = $this->getMockBuilder(FormInterface::class)->getMock();
421+
$form->expects($this->once())->method('createView')->willReturn($formView);
422+
423+
$twig = $this->getMockBuilder(Environment::class)->disableOriginalConstructor()->getMock();
424+
$twig->expects($this->once())->method('render')->with('foo', ['form' => $formView, 'bar' => 'bar'])->willReturn('bar');
425+
426+
$container = new Container();
427+
$container->set('twig', $twig);
428+
429+
$controller = $this->createController();
430+
$controller->setContainer($container);
431+
432+
$response = $controller->renderForm('foo', $form, ['bar' => 'bar']);
433+
434+
$this->assertTrue($response->isSuccessful());
435+
$this->assertSame('bar', $response->getContent());
436+
}
437+
438+
public function testRenderInvalidFormTwig()
439+
{
440+
$formView = new FormView();
441+
442+
$form = $this->getMockBuilder(FormInterface::class)->getMock();
443+
$form->expects($this->once())->method('createView')->willReturn($formView);
444+
$form->expects($this->once())->method('isSubmitted')->willReturn(true);
445+
$form->expects($this->once())->method('isValid')->willReturn(false);
446+
447+
$twig = $this->getMockBuilder(Environment::class)->disableOriginalConstructor()->getMock();
448+
$twig->expects($this->once())->method('render')->with('foo', ['form' => $formView, 'bar' => 'bar'])->willReturn('bar');
449+
450+
$container = new Container();
451+
$container->set('twig', $twig);
452+
453+
$controller = $this->createController();
454+
$controller->setContainer($container);
455+
456+
$response = $controller->renderForm('foo', $form, ['bar' => 'bar']);
457+
458+
$this->assertSame(Response::HTTP_UNPROCESSABLE_ENTITY, $response->getStatusCode());
459+
$this->assertSame('bar', $response->getContent());
460+
}
461+
413462
public function testRedirectToRoute()
414463
{
415464
$router = $this->getMockBuilder(\Symfony\Component\Routing\RouterInterface::class)->getMock();

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy