From 27f8f50386b66b641da889100b643809d0dfdcd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Tue, 11 May 2021 21:16:56 +0200 Subject: [PATCH] [FrameworkBundle] Introduce AbstractController::renderForm() --- .../Bundle/FrameworkBundle/CHANGELOG.md | 2 +- .../Controller/AbstractController.php | 54 ++++------ .../Controller/AbstractControllerTest.php | 102 ++++++------------ 3 files changed, 51 insertions(+), 107 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 27ce0f6b81024..6e0c4b971b858 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -7,7 +7,7 @@ CHANGELOG * Deprecate the `session.storage` alias and `session.storage.*` services, use the `session.storage.factory` alias and `session.storage.factory.*` services instead * Deprecate the `framework.session.storage_id` configuration option, use the `framework.session.storage_factory_id` configuration option instead * Deprecate the `session` service and the `SessionInterface` alias, use the `Request::getSession()` or the new `RequestStack::getSession()` methods instead - * Added `AbstractController::handleForm()` to handle a form and set the appropriate HTTP status code + * Added `AbstractController::renderForm()` to render a form and set the appropriate HTTP status code * Added support for configuring PHP error level to log levels * Added the `dispatcher` option to `debug:event-dispatcher` * Added the `event_dispatcher.dispatcher` tag diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php b/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php index 869ade4ce843e..43682654e7a88 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php @@ -266,6 +266,24 @@ protected function render(string $view, array $parameters = [], Response $respon return $response; } + /** + * Renders a view for a form. + * + * The FormView instance is passed to the template in a variable named + * "form" (can be changed via $formVar argument). + * If the form is invalid, a 422 status code is returned. + */ + protected function renderForm(string $view, FormInterface $form, array $parameters = [], Response $response = null, string $formVar = 'form'): Response + { + $response = $this->render($view, [$formVar => $form->createView()] + $parameters, $response); + + if ($form->isSubmitted() && !$form->isValid()) { + $response->setStatusCode(422); + } + + return $response; + } + /** * Streams a view. */ @@ -290,42 +308,6 @@ protected function stream(string $view, array $parameters = [], StreamedResponse return $response; } - /** - * Handles a form. - * - * * if the form is not submitted, $render is called - * * if the form is submitted but invalid, $render is called and a 422 HTTP status code is set if the current status hasn't been customized - * * if the form is submitted and valid, $onSuccess is called, usually this method saves the data and returns a 303 HTTP redirection - * - * For both callables, instead of "mixed", you can use your form's data class as a type-hint for argument #2. - * - * @param callable(FormInterface, mixed, Request): Response $onSuccess - * @param callable(FormInterface, mixed, Request): Response $render - */ - public function handleForm(FormInterface $form, Request $request, callable $onSuccess, callable $render): Response - { - $form->handleRequest($request); - - $submitted = $form->isSubmitted(); - $data = $form->getData(); - - if ($isValid = $submitted && $form->isValid()) { - $response = $onSuccess($form, $data, $request); - } else { - $response = $render($form, $data, $request); - - if ($response instanceof Response && $submitted && 200 === $response->getStatusCode()) { - $response->setStatusCode(Response::HTTP_UNPROCESSABLE_ENTITY); - } - } - - if (!$response instanceof Response) { - throw new \TypeError(sprintf('The "%s" callable passed to "%s::handleForm()" must return a Response, "%s" returned.', $isValid ? '$onSuccess' : '$render', get_debug_type($this), get_debug_type($response))); - } - - return $response; - } - /** * Returns a NotFoundHttpException. * diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php index ef36f3acd0e8e..97b31bf28bfc3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php @@ -23,6 +23,7 @@ use Symfony\Component\Form\FormConfigInterface; use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; use Symfony\Component\HttpFoundation\File\File; @@ -411,9 +412,15 @@ public function testRenderTwig() $this->assertEquals('bar', $controller->render('foo')->getContent()); } - public function testStreamTwig() + public function testRenderFormNew() { - $twig = $this->createMock(Environment::class); + $formView = new FormView(); + + $form = $this->getMockBuilder(FormInterface::class)->getMock(); + $form->expects($this->once())->method('createView')->willReturn($formView); + + $twig = $this->getMockBuilder(Environment::class)->disableOriginalConstructor()->getMock(); + $twig->expects($this->once())->method('render')->with('foo', ['form' => $formView, 'bar' => 'bar'])->willReturn('bar'); $container = new Container(); $container->set('twig', $twig); @@ -421,92 +428,47 @@ public function testStreamTwig() $controller = $this->createController(); $controller->setContainer($container); - $this->assertInstanceOf(StreamedResponse::class, $controller->stream('foo')); - } - - public function testHandleFormNotSubmitted() - { - $form = $this->createMock(FormInterface::class); - $form->expects($this->once())->method('isSubmitted')->willReturn(false); - - $controller = $this->createController(); - $response = $controller->handleForm( - $form, - Request::create('https://example.com'), - function (FormInterface $form, $data, Request $request): Response { - return new RedirectResponse('https://example.com/redir', Response::HTTP_SEE_OTHER); - }, - function (FormInterface $form, $data, Request $request): Response { - return new Response('rendered'); - } - ); + $response = $controller->renderForm('foo', $form, ['bar' => 'bar']); $this->assertTrue($response->isSuccessful()); - $this->assertSame('rendered', $response->getContent()); + $this->assertSame('bar', $response->getContent()); } - public function testHandleFormInvalid() + public function testRenderFormSubmittedAndInvalid() { - $form = $this->createMock(FormInterface::class); + $formView = new FormView(); + + $form = $this->getMockBuilder(FormInterface::class)->getMock(); + $form->expects($this->once())->method('createView')->willReturn($formView); $form->expects($this->once())->method('isSubmitted')->willReturn(true); $form->expects($this->once())->method('isValid')->willReturn(false); - $controller = $this->createController(); - $response = $controller->handleForm( - $form, - Request::create('https://example.com'), - function (FormInterface $form, $data, Request $request): Response { - return new RedirectResponse('https://example.com/redir', Response::HTTP_SEE_OTHER); - }, - function (FormInterface $form, $data, Request $request): Response { - return new Response('rendered'); - } - ); - - $this->assertSame(Response::HTTP_UNPROCESSABLE_ENTITY, $response->getStatusCode()); - $this->assertSame('rendered', $response->getContent()); - } + $twig = $this->getMockBuilder(Environment::class)->disableOriginalConstructor()->getMock(); + $twig->expects($this->once())->method('render')->with('foo', ['myForm' => $formView, 'bar' => 'bar'])->willReturn('bar'); - public function testHandleFormValid() - { - $form = $this->createMock(FormInterface::class); - $form->expects($this->once())->method('isSubmitted')->willReturn(true); - $form->expects($this->once())->method('isValid')->willReturn(true); + $container = new Container(); + $container->set('twig', $twig); $controller = $this->createController(); - $response = $controller->handleForm( - $form, - Request::create('https://example.com'), - function (FormInterface $form, $data, Request $request): Response { - return new RedirectResponse('https://example.com/redir', Response::HTTP_SEE_OTHER); - }, - function (FormInterface $form, $data, Request $request): Response { - return new Response('rendered'); - } - ); + $controller->setContainer($container); - $this->assertInstanceOf(RedirectResponse::class, $response); - $this->assertSame(Response::HTTP_SEE_OTHER, $response->getStatusCode()); - $this->assertSame('https://example.com/redir', $response->getTargetUrl()); + $response = $controller->renderForm('foo', $form, ['bar' => 'bar'], null, 'myForm'); + + $this->assertSame(422, $response->getStatusCode()); + $this->assertSame('bar', $response->getContent()); } - public function testHandleFormTypeError() + public function testStreamTwig() { - $form = $this->createMock(FormInterface::class); - $form->expects($this->once())->method('isSubmitted')->willReturn(true); - $form->expects($this->once())->method('isValid')->willReturn(false); + $twig = $this->createMock(Environment::class); - $controller = $this->createController(); + $container = new Container(); + $container->set('twig', $twig); - $this->expectException(\TypeError::class); - $this->expectExceptionMessage('The "$render" callable passed to "Symfony\Bundle\FrameworkBundle\Tests\Controller\TestAbstractController::handleForm()" must return a Response, "string" returned.'); + $controller = $this->createController(); + $controller->setContainer($container); - $response = $controller->handleForm( - $form, - Request::create('https://example.com'), - function () { return 'abc'; }, - function () { return 'abc'; } - ); + $this->assertInstanceOf(StreamedResponse::class, $controller->stream('foo')); } public function testRedirectToRoute()