8000 feature #39843 [FrameworkBundle] Add renderForm() helper setting the … · symfony/symfony@fa87194 · GitHub
[go: up one dir, main page]

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 49B6
public function testRedirectToRoute()
414463
{
415464
$router = $this->getMockBuilder(\Symfony\Component\Routing\RouterInterface::class)->getMock();

0 commit comments

Comments
 (0)
0