From b79532ab0e7168cf805a117ee6f17fa05ffeb1c9 Mon Sep 17 00:00:00 2001 From: Yonel Ceruto Date: Wed, 21 Aug 2019 02:21:47 -0400 Subject: [PATCH] Add ErrorController to preview and render errors --- UPGRADE-4.4.md | 25 +++- UPGRADE-5.0.md | 4 +- .../Bundle/FrameworkBundle/CHANGELOG.md | 3 +- .../DependencyInjection/Configuration.php | 4 +- .../FrameworkExtension.php | 1 + .../Resources/config/debug_prod.xml | 2 - .../Resources/config/routing/errors.xml | 12 ++ .../Resources/config/schema/symfony-1.0.xsd | 1 + .../FrameworkBundle/Resources/config/web.xml | 14 ++ .../Fixture/TestAppKernel.php | 2 +- .../DependencyInjection/ConfigurationTest.php | 1 + .../Functional/app/BundlePaths/config.yml | 2 +- .../Tests/Functional/app/Fragment/config.yml | 2 +- .../Functional/MissingUserProviderTest.php | 8 +- .../Functional/app/ExceptionController.php | 37 ------ .../Functional/app/JsonLogin/bundles.php | 1 - .../Tests/Functional/app/JsonLogin/config.yml | 2 +- .../app/JsonLogin/custom_handlers.yml | 2 +- .../Tests/Functional/app/config/twig.yml | 2 +- src/Symfony/Bundle/TwigBundle/CHANGELOG.md | 5 +- .../Controller/ExceptionController.php | 4 +- .../Controller/PreviewErrorController.php | 16 +-- .../Compiler/ExceptionListenerPass.php | 19 ++- .../DependencyInjection/Configuration.php | 10 +- .../TwigBundle/Resources/config/twig.xml | 3 +- .../Controller/PreviewErrorControllerTest.php | 3 + .../DependencyInjection/ConfigurationTest.php | 8 +- .../php/customTemplateEscapingGuesser.php | 2 +- .../Fixtures/php/empty.php | 2 +- .../Fixtures/php/formats.php | 2 +- .../DependencyInjection/Fixtures/php/full.php | 2 +- .../DependencyInjection/TwigExtensionTest.php | 2 +- .../Functional/NoTemplatingEntryTest.php | 2 +- src/Symfony/Component/HttpKernel/CHANGELOG.md | 1 + .../HttpKernel/Controller/ErrorController.php | 67 ++++++++++ .../EventListener/DebugHandlersListener.php | 41 +----- .../Tests/Controller/ErrorControllerTest.php | 123 ++++++++++++++++++ .../DebugHandlersListenerTest.php | 1 - 38 files changed, 308 insertions(+), 130 deletions(-) create mode 100644 src/Symfony/Bundle/FrameworkBundle/Resources/config/routing/errors.xml delete mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/ExceptionController.php create mode 100644 src/Symfony/Component/HttpKernel/Controller/ErrorController.php create mode 100644 src/Symfony/Component/HttpKernel/Tests/Controller/ErrorControllerTest.php diff --git a/UPGRADE-4.4.md b/UPGRADE-4.4.md index 3c04fc45bed37..aeaedc3c85011 100644 --- a/UPGRADE-4.4.md +++ b/UPGRADE-4.4.md @@ -217,9 +217,25 @@ TwigBridge TwigBundle ---------- - * Deprecated default value `twig.controller.exception::showAction` of the `twig.exception_controller` configuration option, - set it to `null` instead. This will also change the default error response format according to https://tools.ietf.org/html/rfc7807 - for `json`, `xml`, `atom` and `txt` formats: + * Deprecated `twig.exception_controller` configuration option, set it to "null" and use `framework.error_controller` instead: + + Before: + ```yaml + twig: + exception_controller: 'App\Controller\MyExceptionController' + ``` + + After: + ```yaml + twig: + exception_controller: null + + framework: + error_controller: 'App\Controller\MyExceptionController' + ``` + + The new default exception controller will also change the error response content according to + https://tools.ietf.org/html/rfc7807 for `json`, `xml`, `atom` and `txt` formats: Before: ```json @@ -240,7 +256,8 @@ TwigBundle } ``` - * Deprecated the `ExceptionController` and all built-in error templates, use the error renderer mechanism of the `ErrorRenderer` component + * Deprecated the `ExceptionController` and `PreviewErrorController` controllers, use `ErrorController` from the HttpKernel component instead + * Deprecated all built-in error templates, use the error renderer mechanism of the `ErrorRenderer` component * Deprecated loading custom error templates in non-html formats. Custom HTML error pages based on Twig keep working as before: Before (`templates/bundles/TwigBundle/Exception/error.jsonld.twig`): diff --git a/UPGRADE-5.0.md b/UPGRADE-5.0.md index 94ec159545e7f..4059931105ac3 100644 --- a/UPGRADE-5.0.md +++ b/UPGRADE-5.0.md @@ -538,8 +538,8 @@ TwigBundle * The default value (`false`) of the `twig.strict_variables` configuration option has been changed to `%kernel.debug%`. * The `transchoice` tag and filter have been removed, use the `trans` ones instead with a `%count%` parameter. * Removed support for legacy templates directories `src/Resources/views/` and `src/Resources//views/`, use `templates/` and `templates/bundles//` instead. - * The default value (`twig.controller.exception::showAction`) of the `twig.exception_controller` configuration option has been changed to `null`. - * Removed `ExceptionController` class and all built-in error templates + * The `twig.exception_controller` configuration option has been removed, use `framework.error_controller` instead. + * Removed `ExceptionController`, `PreviewErrorController` classes and all built-in error templates TwigBridge ---------- diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 446a26a3e0a4d..9fe3103732069 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -14,7 +14,8 @@ CHANGELOG * Deprecated `routing.loader.service`, use `routing.loader.container` instead. * Not tagging service route loaders with `routing.route_loader` has been deprecated. * Overriding the methods `KernelTestCase::tearDown()` and `WebTestCase::tearDown()` without the `void` return-type is deprecated. - + * Added new `error_controller` configuration to handle system exceptions + 4.3.0 ----- diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 84dd91b0ef061..bf16ddc96f9cc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -28,7 +28,6 @@ use Symfony\Component\Mailer\Mailer; use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; -use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Serializer\Serializer; use Symfony\Component\Translation\Translator; use Symfony\Component\Validator\Validation; @@ -84,6 +83,9 @@ public function getConfigTreeBuilder() ->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end() ->prototype('scalar')->end() ->end() + ->scalarNode('error_controller') + ->defaultValue('error_controller') + ->end() ->end() ; diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 0fa3211e33fb7..309a48f2442a0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -212,6 +212,7 @@ public function load(array $configs, ContainerBuilder $container) $container->setParameter('kernel.http_method_override', $config['http_method_override']); $container->setParameter('kernel.trusted_hosts', $config['trusted_hosts']); $container->setParameter('kernel.default_locale', $config['default_locale']); + $container->setParameter('kernel.error_controller', $config['error_controller']); if (!$container->hasParameter('debug.file_link_format')) { if (!$container->hasParameter('templating.helper.code.file_link_format')) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug_prod.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug_prod.xml index f95b218d52ded..786158dd899e1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug_prod.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug_prod.xml @@ -21,8 +21,6 @@ %kernel.debug% %kernel.debug% - %kernel.charset% - diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing/errors.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing/errors.xml new file mode 100644 index 0000000000000..13a9cc4076c79 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing/errors.xml @@ -0,0 +1,12 @@ + + + + + + error_controller::preview + html + \d+ + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index f1dae61035fc4..ae1c75dcc96eb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -41,6 +41,7 @@ + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml index 8cc62a72a68e5..ddbab05b42e21 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml @@ -88,5 +88,19 @@ + + + + %kernel.error_controller% + + + + + + + %kernel.error_controller% + + %kernel.debug% + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CacheClearCommand/Fixture/TestAppKernel.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CacheClearCommand/Fixture/TestAppKernel.php index 26d03ef9b99b6..d6a58798c1369 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CacheClearCommand/Fixture/TestAppKernel.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CacheClearCommand/Fixture/TestAppKernel.php @@ -38,7 +38,7 @@ public function registerContainerConfiguration(LoaderInterface $loader) public function setAnnotatedClassCache(array $annotatedClasses) { - $annotatedClasses = array_diff($annotatedClasses, ['Symfony\Bundle\WebProfilerBundle\Controller\ExceptionController', 'Symfony\Bundle\TwigBundle\Controller\ExceptionController']); + $annotatedClasses = array_diff($annotatedClasses, ['Symfony\Bundle\WebProfilerBundle\Controller\ExceptionController', 'Symfony\Bundle\TwigBundle\Controller\ExceptionController', 'Symfony\Bundle\TwigBundle\Controller\PreviewErrorController']); parent::setAnnotatedClassCache($annotatedClasses); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index efead383b11cf..f2faf79d69094 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -373,6 +373,7 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor 'transports' => [], 'enabled' => !class_exists(FullStack::class) && class_exists(Mailer::class), ], + 'error_controller' => 'error_controller', ]; } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/BundlePaths/config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/BundlePaths/config.yml index 4a2d4c57ef6a4..3e1e53738cf93 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/BundlePaths/config.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/BundlePaths/config.yml @@ -8,4 +8,4 @@ framework: twig: strict_variables: '%kernel.debug%' - exception_controller: ~ + exception_controller: null # to be removed in 5.0 diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Fragment/config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Fragment/config.yml index f48b4444fbde4..ceeea37a1001b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Fragment/config.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Fragment/config.yml @@ -7,4 +7,4 @@ framework: twig: strict_variables: '%kernel.debug%' - exception_controller: ~ + exception_controller: null # to be removed in 5.0 diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/MissingUserProviderTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/MissingUserProviderTest.php index 6231bde6414d5..47d68b4f682d5 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/MissingUserProviderTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/MissingUserProviderTest.php @@ -15,13 +15,15 @@ class MissingUserProviderTest extends AbstractWebTestCase { public function testUserProviderIsNeeded() { - $this->expectException('Symfony\Component\Config\Definition\Exception\InvalidConfigurationException'); - $this->expectExceptionMessage('"default" firewall requires a user provider but none was defined.'); - $client = $this->createClient(['test_case' => 'MissingUserProvider', 'root_config' => 'config.yml']); + $client = $this->createClient(['test_case' => 'MissingUserProvider', 'root_config' => 'config.yml', 'debug' => true]); $client->request('GET', '/', [], [], [ 'PHP_AUTH_USER' => 'username', 'PHP_AUTH_PW' => 'pa$$word', ]); + + $response = $client->getResponse(); + $this->assertSame(500, $response->getStatusCode()); + $this->stringContains('"default" firewall requires a user provider but none was defined.', $response->getContent()); } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/ExceptionController.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/ExceptionController.php deleted file mode 100644 index 7a22a599b74d7..0000000000000 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/ExceptionController.php +++ /dev/null @@ -1,37 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\SecurityBundle\Tests\Functional\app; - -use Symfony\Component\ErrorRenderer\ErrorRenderer; -use Symfony\Component\ErrorRenderer\ErrorRenderer\HtmlErrorRenderer; -use Symfony\Component\ErrorRenderer\ErrorRenderer\JsonErrorRenderer; -use Symfony\Component\ErrorRenderer\Exception\FlattenException; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; - -class ExceptionController -{ - private $errorRenderer; - - public function __construct() - { - $this->errorRenderer = new ErrorRenderer([ - new HtmlErrorRenderer(), - new JsonErrorRenderer(), - ]); - } - - public function __invoke(Request $request, FlattenException $exception) - { - return new Response($this->errorRenderer->render($exception, $request->getPreferredFormat()), $exception->getStatusCode()); - } -} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/bundles.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/bundles.php index 7dbd6e438072f..cd367a95b477b 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/bundles.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/bundles.php @@ -12,6 +12,5 @@ return [ new Symfony\Bundle\SecurityBundle\SecurityBundle(), new Symfony\Bundle\FrameworkBundle\FrameworkBundle(), - new Symfony\Bundle\TwigBundle\TwigBundle(), new Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\JsonLoginBundle\JsonLoginBundle(), ]; diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/config.yml index cf92920f4bc25..d6ed10e896ff9 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/config.yml @@ -1,5 +1,5 @@ imports: - - { resource: ./../config/default.yml } + - { resource: ./../config/framework.yml } security: encoders: diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/custom_handlers.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/custom_handlers.yml index dff93273e804b..e15e203c626cc 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/custom_handlers.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/custom_handlers.yml @@ -1,5 +1,5 @@ imports: - - { resource: ./../config/default.yml } + - { resource: ./../config/framework.yml } security: encoders: diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/config/twig.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/config/twig.yml index e53084cda7c01..f578e4b510378 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/config/twig.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/config/twig.yml @@ -2,4 +2,4 @@ twig: debug: '%kernel.debug%' strict_variables: '%kernel.debug%' - exception_controller: Symfony\Bundle\SecurityBundle\Tests\Functional\app\ExceptionController + exception_controller: null # to be removed in 5.0 diff --git a/src/Symfony/Bundle/TwigBundle/CHANGELOG.md b/src/Symfony/Bundle/TwigBundle/CHANGELOG.md index eb1f93246a346..99b14db8d52c5 100644 --- a/src/Symfony/Bundle/TwigBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/TwigBundle/CHANGELOG.md @@ -7,8 +7,9 @@ CHANGELOG * marked the `TemplateIterator` as `internal` * added HTML comment to beginning and end of `exception_full.html.twig` * added a new `TwigHtmlErrorRenderer` for `html` format, integrated with the `ErrorRenderer` component - * deprecated `ExceptionController` class and all built-in error templates in favor of the new error renderer mechanism - * deprecated default value `twig.controller.exception::showAction` of `twig.exception_controller` configuration option, set it to `null` instead + * deprecated `ExceptionController` and `PreviewErrorController` controllers, use `ErrorController` from the `HttpKernel` component instead + * deprecated all built-in error templates in favor of the new error renderer mechanism + * deprecated `twig.exception_controller` configuration option, set it to "null" and use `framework.error_controller` configuration instead 4.2.0 ----- diff --git a/src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php b/src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php index 003bf47c4d63a..8c6174f876bfc 100644 --- a/src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php +++ b/src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php @@ -19,7 +19,7 @@ use Twig\Error\LoaderError; use Twig\Loader\ExistsLoaderInterface; -@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use the ErrorRenderer component instead.', ExceptionController::class), E_USER_DEPRECATED); +@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', ExceptionController::class, \Symfony\Component\HttpKernel\Controller\ErrorController::class), E_USER_DEPRECATED); /** * ExceptionController renders error or exception pages for a given @@ -28,7 +28,7 @@ * @author Fabien Potencier * @author Matthias Pigulla * - * @deprecated since Symfony 4.4, use the ErrorRenderer component instead. + * @deprecated since Symfony 4.4, use Symfony\Component\HttpKernel\Controller\ErrorController instead. */ class ExceptionController { diff --git a/src/Symfony/Bundle/TwigBundle/Controller/PreviewErrorController.php b/src/Symfony/Bundle/TwigBundle/Controller/PreviewErrorController.php index bc15a968e9c15..7e82c3e68a9a2 100644 --- a/src/Symfony/Bundle/TwigBundle/Controller/PreviewErrorController.php +++ b/src/Symfony/Bundle/TwigBundle/Controller/PreviewErrorController.php @@ -11,39 +11,35 @@ namespace Symfony\Bundle\TwigBundle\Controller; -use Symfony\Component\ErrorRenderer\ErrorRenderer; use Symfony\Component\ErrorRenderer\Exception\FlattenException; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\HttpKernelInterface; +@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use the "%s" instead.', PreviewErrorController::class, \Symfony\Component\HttpKernel\Controller\ErrorController::class), E_USER_DEPRECATED); + /** * PreviewErrorController can be used to test error pages. * * It will create a test exception and forward it to another controller. * * @author Matthias Pigulla + * + * @deprecated since Symfony 4.4, use the Symfony\Component\HttpKernel\Controller\ErrorController instead. */ class PreviewErrorController { protected $kernel; protected $controller; - private $errorRenderer; - public function __construct(HttpKernelInterface $kernel, $controller, ErrorRenderer $errorRenderer = null) + public function __construct(HttpKernelInterface $kernel, $controller) { $this->kernel = $kernel; $this->controller = $controller; - $this->errorRenderer = $errorRenderer; } public function previewErrorPageAction(Request $request, $code) { - $exception = FlattenException::createFromThrowable(new \Exception('Something has intentionally gone wrong.'), $code, ['X-Debug' => false]); - - if (null === $this->controller && null !== $this->errorRenderer) { - return new Response($this->errorRenderer->render($exception, $request->getPreferredFormat()), $code); - } + $exception = FlattenException::createFromThrowable(new \Exception('Something has intentionally gone wrong.'), $code); /* * This Request mimics the parameters set by diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExceptionListenerPass.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExceptionListenerPass.php index ff5a0e220796e..d06b8e8199c20 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExceptionListenerPass.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExceptionListenerPass.php @@ -18,6 +18,8 @@ * Registers the Twig exception listener if Twig is registered as a templating engine. * * @author Fabien Potencier + * + * @internal */ class ExceptionListenerPass implements CompilerPassInterface { @@ -27,13 +29,18 @@ public function process(ContainerBuilder $container) return; } - // register the exception controller only if Twig is enabled and required dependencies do exist - if (!class_exists('Symfony\Component\ErrorRenderer\Exception\FlattenException') || !interface_exists('Symfony\Component\EventDispatcher\EventSubscriberInterface')) { + // to be removed in 5.0 + // register the exception listener only if it's currently used, else use the provided by FrameworkBundle + if (null === $container->getParameter('twig.exception_listener.controller') && $container->hasDefinition('exception_listener')) { $container->removeDefinition('twig.exception_listener'); - } elseif ($container->hasParameter('templating.engines')) { - $engines = $container->getParameter('templating.engines'); - if (!\in_array('twig', $engines)) { - $container->removeDefinition('twig.exception_listener'); + } else { + $container->removeDefinition('exception_listener'); + + if ($container->hasParameter('templating.engines')) { + $engines = $container->getParameter('templating.engines'); + if (!\in_array('twig', $engines, true)) { + $container->removeDefinition('twig.exception_listener'); + } } } } diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php index ebbf8d3d325f8..99be854a0cc45 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php @@ -36,10 +36,18 @@ public function getConfigTreeBuilder() ->children() ->scalarNode('exception_controller') ->defaultValue(static function () { - @trigger_error('Relying on the default value ("twig.controller.exception::showAction") of the "twig.exception_controller" configuration option is deprecated since Symfony 4.4, set it to "null" explicitly instead, which will be the new default in 5.0.', E_USER_DEPRECATED); + @trigger_error('The "twig.exception_controller" configuration key has been deprecated in Symfony 4.4, set it to "null" and use "framework.error_controller" configuration key instead.', E_USER_DEPRECATED); return 'twig.controller.exception::showAction'; }) + ->validate() + ->ifTrue(static function ($v) { return null !== $v; }) + ->then(static function ($v) { + @trigger_error('The "twig.exception_controller" configuration key has been deprecated in Symfony 4.4, set it to "null" and use "framework.error_controller" configuration key instead.', E_USER_DEPRECATED); + + return $v; + }) + ->end() ->end() ->end() ; diff --git a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml index 52723177a1fbf..fa886981e9000 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml +++ b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml @@ -134,6 +134,7 @@ %twig.exception_listener.controller% %kernel.debug% + The "%service_id%" service is deprecated since Symfony 4.4. @@ -145,7 +146,7 @@ %twig.exception_listener.controller% - + The "%service_id%" service is deprecated since Symfony 4.4. diff --git a/src/Symfony/Bundle/TwigBundle/Tests/Controller/PreviewErrorControllerTest.php b/src/Symfony/Bundle/TwigBundle/Tests/Controller/PreviewErrorControllerTest.php index f007e630e6147..0178276ee5d89 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/Controller/PreviewErrorControllerTest.php +++ b/src/Symfony/Bundle/TwigBundle/Tests/Controller/PreviewErrorControllerTest.php @@ -18,6 +18,9 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\HttpKernelInterface; +/** + * @group legacy + */ class PreviewErrorControllerTest extends TestCase { public function testForwardRequestToConfiguredController() diff --git a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/ConfigurationTest.php index 33300336d11c3..522db25a6cfe3 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -21,7 +21,7 @@ public function testDoNoDuplicateDefaultFormResources() { $input = [ 'strict_variables' => false, // to be removed in 5.0 relying on default - 'exception_controller' => null, // to be removed in 5.0 relying on default + 'exception_controller' => null, // to be removed in 5.0 'form_themes' => ['form_div_layout.html.twig'], ]; @@ -45,14 +45,14 @@ public function testGetStrictVariablesDefaultFalse() /** * @group legacy - * @expectedDeprecation Relying on the default value ("twig.controller.exception::showAction") of the "twig.exception_controller" configuration option is deprecated since Symfony 4.4, set it to "null" explicitly instead, which will be the new default in 5.0. + * @expectedDeprecation The "twig.exception_controller" configuration key has been deprecated in Symfony 4.4, set it to "null" and use "framework.error_controller" configuration key instead. */ public function testGetExceptionControllerDefault() { $processor = new Processor(); - $config = $processor->processConfiguration(new Configuration(), [[]]); + $config = $processor->processConfiguration(new Configuration(), [['exception_controller' => 'exception_controller']]); - $this->assertSame('twig.controller.exception::showAction', $config['exception_controller']); + $this->assertSame('exception_controller', $config['exception_controller']); } public function testGlobalsAreNotNormalized() diff --git a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/php/customTemplateEscapingGuesser.php b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/php/customTemplateEscapingGuesser.php index 481f57cdc5a91..ab5cf941c0311 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/php/customTemplateEscapingGuesser.php +++ b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/php/customTemplateEscapingGuesser.php @@ -4,5 +4,5 @@ 'autoescape_service' => 'my_project.some_bundle.template_escaping_guesser', 'autoescape_service_method' => 'guess', 'strict_variables' => false, // to be removed in 5.0 relying on default - 'exception_controller' => null, // to be removed in 5.0 relying on default + 'exception_controller' => null, // to be removed in 5.0 ]); diff --git a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/php/empty.php b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/php/empty.php index e4d9638c52920..fcc1402151ecb 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/php/empty.php +++ b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/php/empty.php @@ -2,5 +2,5 @@ $container->loadFromExtension('twig', [ 'strict_variables' => false, // to be removed in 5.0 relying on default - 'exception_controller' => null, // to be removed in 5.0 relying on default + 'exception_controller' => null, // to be removed in 5.0 ]); diff --git a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/php/formats.php b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/php/formats.php index 907217bf4040f..c4383a671a626 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/php/formats.php +++ b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/php/formats.php @@ -12,5 +12,5 @@ 'thousands_separator' => '.', ], 'strict_variables' => false, // to be removed in 5.0 relying on default - 'exception_controller' => null, // to be removed in 5.0 relying on default + 'exception_controller' => null, // to be removed in 5.0 ]); diff --git a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/php/full.php b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/php/full.php index 5356e4434725e..18d0ba50f90f2 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/php/full.php +++ b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Fixtures/php/full.php @@ -17,7 +17,7 @@ 'charset' => 'ISO-8859-1', 'debug' => true, 'strict_variables' => true, - 'exception_controller' => null, + 'exception_controller' => null, // to be removed in 5.0 'default_path' => '%kernel.project_dir%/Fixtures/templates', 'paths' => [ 'path1', diff --git a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php index 56c87c7d23527..01abbd166715d 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php +++ b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php @@ -32,7 +32,7 @@ public function testLoadEmptyConfiguration() $container->registerExtension(new TwigExtension()); $container->loadFromExtension('twig', [ 'strict_variables' => false, // to be removed in 5.0 relying on default - 'exception_controller' => null, // to be removed in 5.0 relying on default + 'exception_controller' => null, // to be removed in 5.0 ]); $this->compileContainer($container); diff --git a/src/Symfony/Bundle/TwigBundle/Tests/Functional/NoTemplatingEntryTest.php b/src/Symfony/Bundle/TwigBundle/Tests/Functional/NoTemplatingEntryTest.php index 75bc4297b1cb2..0961ffe22c58c 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/Functional/NoTemplatingEntryTest.php +++ b/src/Symfony/Bundle/TwigBundle/Tests/Functional/NoTemplatingEntryTest.php @@ -68,7 +68,7 @@ public function registerContainerConfiguration(LoaderInterface $loader) ]) ->loadFromExtension('twig', [ 'strict_variables' => false, // to be removed in 5.0 relying on default - 'exception_controller' => null, // to be removed in 5.0 relying on default + 'exception_controller' => null, // to be removed in 5.0 'default_path' => __DIR__.'/templates', ]) ; diff --git a/src/Symfony/Component/HttpKernel/CHANGELOG.md b/src/Symfony/Component/HttpKernel/CHANGELOG.md index 383f155cc79a6..5eb335ac309f2 100644 --- a/src/Symfony/Component/HttpKernel/CHANGELOG.md +++ b/src/Symfony/Component/HttpKernel/CHANGELOG.md @@ -13,6 +13,7 @@ CHANGELOG current directory or with a glob pattern. The fallback directories have never been advocated so you likely do not use those in any app based on the SF Standard or Flex edition. * Marked all dispatched event classes as `@final` + * Added `ErrorController` to enable the preview and error rendering mechanism 4.3.0 ----- diff --git a/src/Symfony/Component/HttpKernel/Controller/ErrorController.php b/src/Symfony/Component/HttpKernel/Controller/ErrorController.php new file mode 100644 index 0000000000000..3efa4e96dca78 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Controller/ErrorController.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Symfony\Component\ErrorRenderer\ErrorRenderer; +use Symfony\Component\ErrorRenderer\Exception\ErrorRendererNotFoundException; +use Symfony\Component\ErrorRenderer\Exception\FlattenException; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * Renders error or exception pages from a given FlattenException. + * + * @author Yonel Ceruto + * @author Matthias Pigulla + */ +class ErrorController +{ + private $kernel; + private $controller; + private $errorRenderer; + + public function __construct(HttpKernelInterface $kernel, $controller, ErrorRenderer $errorRenderer) + { + $this->kernel = $kernel; + $this->controller = $controller; + $this->errorRenderer = $errorRenderer; + } + + public function __invoke(Request $request, FlattenException $exception): Response + { + try { + return new Response($this->errorRenderer->render($exception, $request->getPreferredFormat()), $exception->getStatusCode(), $exception->getHeaders()); + } catch (ErrorRendererNotFoundException $e) { + return new Response($this->errorRenderer->render($exception), $exception->getStatusCode(), $exception->getHeaders()); + } + } + + public function preview(Request $request, int $code): Response + { + $exception = FlattenException::createFromThrowable(new \Exception('This is a sample exception.'), $code, ['X-Debug' => false]); + + /* + * This Request mimics the parameters set by + * \Symfony\Component\HttpKernel\EventListener\ExceptionListener::duplicateRequest, with + * the additional "showException" flag. + */ + $subRequest = $request->duplicate(null, null, [ + '_controller' => $this->controller, + 'exception' => $exception, + 'logger' => null, + 'showException' => false, + ]); + + return $this->kernel->handle($subRequest, HttpKernelInterface::SUB_REQUEST); + } +} diff --git a/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php b/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php index 182a53667c4c0..8a01569c9a6fe 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php @@ -16,15 +16,9 @@ use Symfony\Component\Console\Event\ConsoleEvent; use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\ErrorHandler\ErrorHandler; -use Symfony\Component\ErrorRenderer\ErrorRenderer; -use Symfony\Component\ErrorRenderer\ErrorRenderer\HtmlErrorRenderer; -use Symfony\Component\ErrorRenderer\Exception\ErrorRendererNotFoundException; use Symfony\Component\EventDispatcher\Event; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; -use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; use Symfony\Component\HttpKernel\Event\KernelEvent; use Symfony\Component\HttpKernel\KernelEvents; @@ -44,8 +38,6 @@ class DebugHandlersListener implements EventSubscriberInterface private $scream; private $fileLinkFormat; private $scope; - private $charset; - private $errorRenderer; private $firstCall = true; private $hasTerminatedWithException; @@ -57,7 +49,7 @@ class DebugHandlersListener implements EventSubscriberInterface * @param string|FileLinkFormatter|null $fileLinkFormat The format for links to source files * @param bool $scope Enables/disables scoping mode */ - public function __construct(callable $exceptionHandler = null, LoggerInterface $logger = null, $levels = E_ALL, ?int $throwAt = E_ALL, bool $scream = true, $fileLinkFormat = null, bool $scope = true, string $charset = null, ErrorRenderer $errorRenderer = null) + public function __construct(callable $exceptionHandler = null, LoggerInterface $logger = null, $levels = E_ALL, ?int $throwAt = E_ALL, bool $scream = true, $fileLinkFormat = null, bool $scope = true) { $this->exceptionHandler = $exceptionHandler; $this->logger = $logger; @@ -66,8 +58,6 @@ public function __construct(callable $exceptionHandler = null, LoggerInterface $ $this->scream = $scream; $this->fileLinkFormat = $fileLinkFormat; $this->scope = $scope; - $this->charset = $charset; - $this->errorRenderer = $errorRenderer; } /** @@ -142,33 +132,6 @@ public function configure(Event $event = null) } } - /** - * @internal - */ - public function onKernelException(GetResponseForExceptionEvent $event) - { - if (!$this->hasTerminatedWithException || !$event->isMasterRequest()) { - return; - } - - $debug = $this->scream && $this->scope; - $controller = function (Request $request) use ($debug) { - if (null === $this->errorRenderer) { - $this->errorRenderer = new ErrorRenderer([new HtmlErrorRenderer($debug, $this->charset, $this->fileLinkFormat)]); - } - - $e = $request->attributes->get('exception'); - - try { - return new Response($this->errorRenderer->render($e, $request->getPreferredFormat()), $e->getStatusCode(), $e->getHeaders()); - } catch (ErrorRendererNotFoundException $_) { - return new Response($this->errorRenderer->render($e), $e->getStatusCode(), $e->getHeaders()); - } - }; - - (new ExceptionListener($controller, $this->logger, $debug))->onKernelException($event); - } - public static function getSubscribedEvents() { $events = [KernelEvents::REQUEST => ['configure', 2048]]; @@ -177,8 +140,6 @@ public static function getSubscribedEvents() $events[ConsoleEvents::COMMAND] = ['configure', 2048]; } - $events[KernelEvents::EXCEPTION] = ['onKernelException', -2048]; - return $events; } } diff --git a/src/Symfony/Component/HttpKernel/Tests/Controller/ErrorControllerTest.php b/src/Symfony/Component/HttpKernel/Tests/Controller/ErrorControllerTest.php new file mode 100644 index 0000000000000..bc37a0e15ecae --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Tests/Controller/ErrorControllerTest.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Controller; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\ErrorRenderer\ErrorRenderer; +use Symfony\Component\ErrorRenderer\ErrorRenderer\HtmlErrorRenderer; +use Symfony\Component\ErrorRenderer\ErrorRenderer\JsonErrorRenderer; +use Symfony\Component\ErrorRenderer\Exception\FlattenException; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ErrorController; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +class ErrorControllerTest extends TestCase +{ + /** + * @dataProvider getInvokeControllerDataProvider + */ + public function testInvokeController(Request $request, FlattenException $exception, int $statusCode, string $content) + { + $kernel = $this->getMockBuilder(HttpKernelInterface::class)->getMock(); + $errorRenderer = new ErrorRenderer([new HtmlErrorRenderer(), new JsonErrorRenderer()]); + $controller = new ErrorController($kernel, null, $errorRenderer); + $response = $controller($request, $exception); + + $this->assertSame($statusCode, $response->getStatusCode()); + self::assertStringContainsString($content, strtr($response->getContent(), ["\n" => '', ' ' => ''])); + } + + public function getInvokeControllerDataProvider() + { + yield 'default status code and HTML format' => [ + new Request(), + FlattenException::createFromThrowable(new \Exception()), + 500, + 'The server returned a "500 Internal Server Error".', + ]; + + yield 'custom status code' => [ + new Request(), + FlattenException::createFromThrowable(new NotFoundHttpException('Page not found.')), + 404, + 'The server returned a "404 Not Found".', + ]; + + $request = new Request(); + $request->attributes->set('_format', 'json'); + yield 'custom format via _format attribute' => [ + $request, + FlattenException::createFromThrowable(new \Exception('foo')), + 500, + '{"title": "Internal Server Error","status": 500,"detail": "foo"}', + ]; + + $request = new Request(); + $request->headers->set('Accept', 'application/json'); + yield 'custom format via Accept header' => [ + $request, + FlattenException::createFromThrowable(new HttpException(405, 'Invalid request.')), + 405, + '{"title": "Method Not Allowed","status": 405,"detail": "Invalid request."}', + ]; + + $request = new Request(); + $request->headers->set('Content-Type', 'application/json'); + yield 'custom format via Content-Type header' => [ + $request, + FlattenException::createFromThrowable(new HttpException(405, 'Invalid request.')), + 405, + '{"title": "Method Not Allowed","status": 405,"detail": "Invalid request."}', + ]; + + $request = new Request(); + $request->attributes->set('_format', 'unknown'); + yield 'default HTML format for unknown formats' => [ + $request, + FlattenException::createFromThrowable(new HttpException(405, 'Invalid request.')), + 405, + 'The server returned a "405 Method Not Allowed".', + ]; + } + + public function testPreviewController() + { + $_controller = 'error_controller'; + $code = 404; + + $kernel = $this->getMockBuilder(HttpKernelInterface::class)->getMock(); + $kernel + ->expects($this->once()) + ->method('handle') + ->with( + $this->callback(function (Request $request) use ($_controller, $code) { + $exception = $request->attributes->get('exception'); + + $this->assertSame($_controller, $request->attributes->get('_controller')); + $this->assertInstanceOf(FlattenException::class, $exception); + $this->assertSame($code, $exception->getStatusCode()); + $this->assertFalse($request->attributes->get('showException')); + + return true; + }), + $this->equalTo(HttpKernelInterface::SUB_REQUEST) + ) + ->willReturn($response = new Response()); + + $controller = new ErrorController($kernel, $_controller, new ErrorRenderer([])); + + $this->assertSame($response, $controller->preview(new Request(), $code)); + } +} diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/DebugHandlersListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/DebugHandlersListenerTest.php index f9c0f6b6b7300..b8ec8f3e73ab2 100644 --- a/src/Symfony/Component/HttpKernel/Tests/EventListener/DebugHandlersListenerTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/DebugHandlersListenerTest.php @@ -101,7 +101,6 @@ public function testConsoleEvent() $xListeners = [ KernelEvents::REQUEST => [[$listener, 'configure']], ConsoleEvents::COMMAND => [[$listener, 'configure']], - KernelEvents::EXCEPTION => [[$listener, 'onKernelException']], ]; $this->assertSame($xListeners, $dispatcher->getListeners()); 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