From 522854606676e312eb6c77cdf56cfad040a9f452 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Tue, 13 Apr 2021 14:52:55 +0200 Subject: [PATCH] [FrameworkBundle] Add AbstractController::handleForm() helper --- .../Bundle/FrameworkBundle/CHANGELOG.md | 2 +- .../Controller/AbstractController.php | 25 ++++-- .../Controller/AbstractControllerTest.php | 77 ++++++++++++------- 3 files changed, 68 insertions(+), 36 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index c82bbe82e2..d9d62d47af 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::renderForm()` to render a form and set the appropriate HTTP status code + * Added `AbstractController::handleForm()` to handle 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 5acdfbe2dd..b0c4f16acd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php @@ -291,15 +291,28 @@ abstract class AbstractController implements ServiceSubscriberInterface } /** - * Renders a form. + * Handles a form. * - * The FormView instance is passed to the template in a variable named "form". - * If the form is invalid, a 422 status code is returned. + * * 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 + * + * @param callable(FormInterface, mixed): Response $onSuccess + * @param callable(FormInterface, mixed): Response $render */ - public function renderForm(string $view, FormInterface $form, array $parameters = [], Response $response = null): Response + public function handleForm(FormInterface $form, Request $request, callable $onSuccess, callable $render): Response { - $response = $this->render($view, ['form' => $form->createView()] + $parameters, $response); - if ($form->isSubmitted() && !$form->isValid()) { + $form->handleRequest($request); + + $submitted = $form->isSubmitted(); + + $data = $form->getData(); + if ($submitted && $form->isValid()) { + return $onSuccess($form, $data); + } + + $response = $render($form, $data); + if ($submitted && 200 === $response->getStatusCode()) { $response->setStatusCode(Response::HTTP_UNPROCESSABLE_ENTITY); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php index f5dfdefe11..ca5c029ea0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php @@ -23,7 +23,6 @@ use Symfony\Component\Form\FormBuilderInterface; 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; @@ -425,50 +424,70 @@ class AbstractControllerTest extends TestCase $this->assertInstanceOf(StreamedResponse::class, $controller->stream('foo')); } - public function testRenderFormTwig() + public function testHandleFormNotSubmitted() { - $formView = new FormView(); - $form = $this->createMock(FormInterface::class); - $form->expects($this->once())->method('createView')->willReturn($formView); - - $twig = $this->createMock(Environment::class); - $twig->expects($this->once())->method('render')->with('foo', ['form' => $formView, 'bar' => 'bar'])->willReturn('bar'); - - $container = new Container(); - $container->set('twig', $twig); + $form->expects($this->once())->method('isSubmitted')->willReturn(false); $controller = $this->createController(); - $controller->setContainer($container); - - $response = $controller->renderForm('foo', $form, ['bar' => 'bar']); + $response = $controller->handleForm( + $form, + Request::create('https://example.com'), + function (FormInterface $form, $data): Response { + return new RedirectResponse('https://example.com/redir', Response::HTTP_SEE_OTHER); + }, + function (FormInterface $form, $data): Response { + return new Response('rendered'); + } + ); $this->assertTrue($response->isSuccessful()); - $this->assertSame('bar', $response->getContent()); + $this->assertSame('rendered', $response->getContent()); } - public function testRenderInvalidFormTwig() + public function testHandleFormInvalid() { - $formView = new FormView(); - $form = $this->createMock(FormInterface::class); - $form->expects($this->once())->method('createView')->willReturn($formView); $form->expects($this->once())->method('isSubmitted')->willReturn(true); $form->expects($this->once())->method('isValid')->willReturn(false); - $twig = $this->createMock(Environment::class); - $twig->expects($this->once())->method('render')->with('foo', ['form' => $formView, 'bar' => 'bar'])->willReturn('bar'); - - $container = new Container(); - $container->set('twig', $twig); - $controller = $this->createController(); - $controller->setContainer($container); - - $response = $controller->renderForm('foo', $form, ['bar' => 'bar']); + $response = $controller->handleForm( + $form, + Request::create('https://example.com'), + function (FormInterface $form): Response { + return new RedirectResponse('https://example.com/redir', Response::HTTP_SEE_OTHER); + }, + function (FormInterface $form): Response { + return new Response('rendered'); + } + ); $this->assertSame(Response::HTTP_UNPROCESSABLE_ENTITY, $response->getStatusCode()); - $this->assertSame('bar', $response->getContent()); + $this->assertSame('rendered', $response->getContent()); + } + + public function testHandleFormValid() + { + $form = $this->createMock(FormInterface::class); + $form->expects($this->once())->method('isSubmitted')->willReturn(true); + $form->expects($this->once())->method('isValid')->willReturn(true); + + $controller = $this->createController(); + $response = $controller->handleForm( + $form, + Request::create('https://example.com'), + function (FormInterface $form): Response { + return new RedirectResponse('https://example.com/redir', Response::HTTP_SEE_OTHER); + }, + function (FormInterface $form): Response { + return new Response('rendered'); + } + ); + + $this->assertInstanceOf(RedirectResponse::class, $response); + $this->assertSame(Response::HTTP_SEE_OTHER, $response->getStatusCode()); + $this->assertSame('https://example.com/redir', $response->getTargetUrl()); } public function testRedirectToRoute()