diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index d10081ef7b..6cb3fde123 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 5.3 --- + * 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 61049b607a..3074af7a17 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php @@ -17,6 +17,7 @@ use Psr\Link\LinkInterface; use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface; use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\Form\Form; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\Form\FormInterface; @@ -289,6 +290,22 @@ abstract class AbstractController implements ServiceSubscriberInterface return $response; } + /** + * Renders 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. + */ + public function renderForm(string $view, FormInterface $form, array $parameters = [], Response $response = null): Response + { + $response = $this->render($view, ['form' => $form->createView()] + $parameters, $response); + if ($form->isSubmitted() && !$form->isValid()) { + $response->setStatusCode(Response::HTTP_UNPROCESSABLE_ENTITY); + } + + 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 9ac0f5e214..ba08a9820f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php @@ -18,6 +18,8 @@ use Symfony\Component\DependencyInjection\ParameterBag\ContainerBag; use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag; use Symfony\Component\Form\Form; use Symfony\Component\Form\FormConfigInterface; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\File\File; use Symfony\Component\HttpFoundation\JsonResponse; @@ -31,6 +33,7 @@ use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\User\User; use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\WebLink\Link; +use Twig\Environment; class AbstractControllerTest extends TestCase { @@ -371,7 +374,7 @@ class AbstractControllerTest extends TestCase public function testRenderViewTwig() { - $twig = $this->getMockBuilder(\Twig\Environment::class)->disableOriginalConstructor()->getMock(); + $twig = $this->getMockBuilder(Environment::class)->disableOriginalConstructor()->getMock(); $twig->expects($this->once())->method('render')->willReturn('bar'); $container = new Container(); @@ -385,7 +388,7 @@ class AbstractControllerTest extends TestCase public function testRenderTwig() { - $twig = $this->getMockBuilder(\Twig\Environment::class)->disableOriginalConstructor()->getMock(); + $twig = $this->getMockBuilder(Environment::class)->disableOriginalConstructor()->getMock(); $twig->expects($this->once())->method('render')->willReturn('bar'); $container = new Container(); @@ -399,7 +402,7 @@ class AbstractControllerTest extends TestCase public function testStreamTwig() { - $twig = $this->getMockBuilder(\Twig\Environment::class)->disableOriginalConstructor()->getMock(); + $twig = $this->getMockBuilder(Environment::class)->disableOriginalConstructor()->getMock(); $container = new Container(); $container->set('twig', $twig); @@ -410,6 +413,52 @@ class AbstractControllerTest extends TestCase $this->assertInstanceOf(\Symfony\Component\HttpFoundation\StreamedResponse::class, $controller->stream('foo')); } + public function testRenderFormTwig() + { + $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); + + $controller = $this->createController(); + $controller->setContainer($container); + + $response = $controller->renderForm('foo', $form, ['bar' => 'bar']); + + $this->assertTrue($response->isSuccessful()); + $this->assertSame('bar', $response->getContent()); + } + + public function testRenderInvalidFormTwig() + { + $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); + + $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); + + $controller = $this->createController(); + $controller->setContainer($container); + + $response = $controller->renderForm('foo', $form, ['bar' => 'bar']); + + $this->assertSame(Response::HTTP_UNPROCESSABLE_ENTITY, $response->getStatusCode()); + $this->assertSame('bar', $response->getContent()); + } + public function testRedirectToRoute() { $router = $this->getMockBuilder(\Symfony\Component\Routing\RouterInterface::class)->getMock();