feature #33271 Added new ErrorController + Preview and enabling there the error renderer mechanism (yceruto)
This PR was merged into the 4.4 branch.
Discussion
----------
Added new ErrorController + Preview and enabling there the error renderer mechanism
| Q | A
| ------------- | ---
| Branch? | 4.4
| Bug fix? | no
| New feature? | yes
| BC breaks? | no
| Deprecations? | yes
| Tests pass? | yes (deps=high failure is normal)
| Fixed tickets | -
| License | MIT
| Doc PR | TODO
After deprecating the `ExceptionController` in TwigBundle (refs https://github.com/symfony/symfony/pull/31398) the `twig.exception_controller` config key becomes useless as feature provided by TwigBundle, while the preview controller is taking more relevance for the error renderer mechanish.
**Proposal**
* Deprecate the `twig.exception_controller` config key in favor of `framework.error_controller` with default `ErrorController` that activates the error renderer mechanism through the current `ExceptionListener`, meaning also that `DebugHandlersListener::onKernelException` method becomes useless too.
* Deprecate the `PreviewErrorController` from TwigBundle in favor of similar in FrameworkBundle.
So you no longer need to install TwigBundle to create a custom error controller or check the preview output of an error renderer (included `TwigHtmlErrorRenderer`).
Btw this would fix https://github.com/symfony/symfony/pull/31398#issuecomment-490081769, removing here workaround in SecurityBundle.
TODO:
- [x] Update CHANGELOG & UPGRADE files
- [x] Add tests
WDYT?
Commits
-------
b79532ab0e
Add ErrorController to preview and render errors
This commit is contained in:
commit
b7371ea5c6
@ -217,9 +217,25 @@ TwigBridge
|
|||||||
TwigBundle
|
TwigBundle
|
||||||
----------
|
----------
|
||||||
|
|
||||||
* Deprecated default value `twig.controller.exception::showAction` of the `twig.exception_controller` configuration option,
|
* Deprecated `twig.exception_controller` configuration option, set it to "null" and use `framework.error_controller` instead:
|
||||||
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:
|
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:
|
Before:
|
||||||
```json
|
```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:
|
* 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`):
|
Before (`templates/bundles/TwigBundle/Exception/error.jsonld.twig`):
|
||||||
|
@ -538,8 +538,8 @@ TwigBundle
|
|||||||
* The default value (`false`) of the `twig.strict_variables` configuration option has been changed to `%kernel.debug%`.
|
* 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.
|
* 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/<BundleName>/views/`, use `templates/` and `templates/bundles/<BundleName>/` instead.
|
* Removed support for legacy templates directories `src/Resources/views/` and `src/Resources/<BundleName>/views/`, use `templates/` and `templates/bundles/<BundleName>/` instead.
|
||||||
* The default value (`twig.controller.exception::showAction`) of the `twig.exception_controller` configuration option has been changed to `null`.
|
* The `twig.exception_controller` configuration option has been removed, use `framework.error_controller` instead.
|
||||||
* Removed `ExceptionController` class and all built-in error templates
|
* Removed `ExceptionController`, `PreviewErrorController` classes and all built-in error templates
|
||||||
|
|
||||||
TwigBridge
|
TwigBridge
|
||||||
----------
|
----------
|
||||||
|
@ -14,6 +14,7 @@ CHANGELOG
|
|||||||
* Deprecated `routing.loader.service`, use `routing.loader.container` instead.
|
* Deprecated `routing.loader.service`, use `routing.loader.container` instead.
|
||||||
* Not tagging service route loaders with `routing.route_loader` has been deprecated.
|
* 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.
|
* 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
|
4.3.0
|
||||||
-----
|
-----
|
||||||
|
@ -28,7 +28,6 @@ use Symfony\Component\Lock\Store\SemaphoreStore;
|
|||||||
use Symfony\Component\Mailer\Mailer;
|
use Symfony\Component\Mailer\Mailer;
|
||||||
use Symfony\Component\Messenger\MessageBusInterface;
|
use Symfony\Component\Messenger\MessageBusInterface;
|
||||||
use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;
|
use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;
|
||||||
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
|
|
||||||
use Symfony\Component\Serializer\Serializer;
|
use Symfony\Component\Serializer\Serializer;
|
||||||
use Symfony\Component\Translation\Translator;
|
use Symfony\Component\Translation\Translator;
|
||||||
use Symfony\Component\Validator\Validation;
|
use Symfony\Component\Validator\Validation;
|
||||||
@ -84,6 +83,9 @@ class Configuration implements ConfigurationInterface
|
|||||||
->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end()
|
->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end()
|
||||||
->prototype('scalar')->end()
|
->prototype('scalar')->end()
|
||||||
->end()
|
->end()
|
||||||
|
->scalarNode('error_controller')
|
||||||
|
->defaultValue('error_controller')
|
||||||
|
->end()
|
||||||
->end()
|
->end()
|
||||||
;
|
;
|
||||||
|
|
||||||
|
@ -212,6 +212,7 @@ class FrameworkExtension extends Extension
|
|||||||
$container->setParameter('kernel.http_method_override', $config['http_method_override']);
|
$container->setParameter('kernel.http_method_override', $config['http_method_override']);
|
||||||
$container->setParameter('kernel.trusted_hosts', $config['trusted_hosts']);
|
$container->setParameter('kernel.trusted_hosts', $config['trusted_hosts']);
|
||||||
$container->setParameter('kernel.default_locale', $config['default_locale']);
|
$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('debug.file_link_format')) {
|
||||||
if (!$container->hasParameter('templating.helper.code.file_link_format')) {
|
if (!$container->hasParameter('templating.helper.code.file_link_format')) {
|
||||||
|
@ -21,8 +21,6 @@
|
|||||||
<argument>%kernel.debug%</argument>
|
<argument>%kernel.debug%</argument>
|
||||||
<argument type="service" id="debug.file_link_formatter" />
|
<argument type="service" id="debug.file_link_formatter" />
|
||||||
<argument>%kernel.debug%</argument>
|
<argument>%kernel.debug%</argument>
|
||||||
<argument>%kernel.charset%</argument>
|
|
||||||
<argument type="service" id="error_renderer" on-invalid="null" />
|
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<service id="debug.file_link_formatter" class="Symfony\Component\HttpKernel\Debug\FileLinkFormatter">
|
<service id="debug.file_link_formatter" class="Symfony\Component\HttpKernel\Debug\FileLinkFormatter">
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
|
||||||
|
<routes xmlns="http://symfony.com/schema/routing"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://symfony.com/schema/routing https://symfony.com/schema/routing/routing-1.0.xsd">
|
||||||
|
|
||||||
|
<route id="_preview_error" path="/{code}.{_format}">
|
||||||
|
<default key="_controller">error_controller::preview</default>
|
||||||
|
<default key="_format">html</default>
|
||||||
|
<requirement key="code">\d+</requirement>
|
||||||
|
</route>
|
||||||
|
</routes>
|
@ -41,6 +41,7 @@
|
|||||||
<xsd:attribute name="secret" type="xsd:string" />
|
<xsd:attribute name="secret" type="xsd:string" />
|
||||||
<xsd:attribute name="default-locale" type="xsd:string" />
|
<xsd:attribute name="default-locale" type="xsd:string" />
|
||||||
<xsd:attribute name="test" type="xsd:boolean" />
|
<xsd:attribute name="test" type="xsd:boolean" />
|
||||||
|
<xsd:attribute name="error-controller" type="xsd:string" />
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
|
|
||||||
<xsd:complexType name="form">
|
<xsd:complexType name="form">
|
||||||
|
@ -88,5 +88,19 @@
|
|||||||
<service id="disallow_search_engine_index_response_listener" class="Symfony\Component\HttpKernel\EventListener\DisallowRobotsIndexingListener">
|
<service id="disallow_search_engine_index_response_listener" class="Symfony\Component\HttpKernel\EventListener\DisallowRobotsIndexingListener">
|
||||||
<tag name="kernel.event_subscriber" />
|
<tag name="kernel.event_subscriber" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<service id="error_controller" class="Symfony\Component\HttpKernel\Controller\ErrorController" public="true">
|
||||||
|
<argument type="service" id="http_kernel" />
|
||||||
|
<argument>%kernel.error_controller%</argument>
|
||||||
|
<argument type="service" id="error_renderer" />
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<service id="exception_listener" class="Symfony\Component\HttpKernel\EventListener\ExceptionListener">
|
||||||
|
<tag name="kernel.event_subscriber" />
|
||||||
|
<tag name="monolog.logger" channel="request" />
|
||||||
|
<argument>%kernel.error_controller%</argument>
|
||||||
|
<argument type="service" id="logger" on-invalid="null" />
|
||||||
|
<argument>%kernel.debug%</argument>
|
||||||
|
</service>
|
||||||
</services>
|
</services>
|
||||||
</container>
|
</container>
|
||||||
|
@ -38,7 +38,7 @@ class TestAppKernel extends Kernel
|
|||||||
|
|
||||||
public function setAnnotatedClassCache(array $annotatedClasses)
|
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);
|
parent::setAnnotatedClassCache($annotatedClasses);
|
||||||
}
|
}
|
||||||
|
@ -373,6 +373,7 @@ class ConfigurationTest extends TestCase
|
|||||||
'transports' => [],
|
'transports' => [],
|
||||||
'enabled' => !class_exists(FullStack::class) && class_exists(Mailer::class),
|
'enabled' => !class_exists(FullStack::class) && class_exists(Mailer::class),
|
||||||
],
|
],
|
||||||
|
'error_controller' => 'error_controller',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,4 +8,4 @@ framework:
|
|||||||
|
|
||||||
twig:
|
twig:
|
||||||
strict_variables: '%kernel.debug%'
|
strict_variables: '%kernel.debug%'
|
||||||
exception_controller: ~
|
exception_controller: null # to be removed in 5.0
|
||||||
|
@ -7,4 +7,4 @@ framework:
|
|||||||
|
|
||||||
twig:
|
twig:
|
||||||
strict_variables: '%kernel.debug%'
|
strict_variables: '%kernel.debug%'
|
||||||
exception_controller: ~
|
exception_controller: null # to be removed in 5.0
|
||||||
|
@ -15,13 +15,15 @@ class MissingUserProviderTest extends AbstractWebTestCase
|
|||||||
{
|
{
|
||||||
public function testUserProviderIsNeeded()
|
public function testUserProviderIsNeeded()
|
||||||
{
|
{
|
||||||
$this->expectException('Symfony\Component\Config\Definition\Exception\InvalidConfigurationException');
|
$client = $this->createClient(['test_case' => 'MissingUserProvider', 'root_config' => 'config.yml', 'debug' => true]);
|
||||||
$this->expectExceptionMessage('"default" firewall requires a user provider but none was defined.');
|
|
||||||
$client = $this->createClient(['test_case' => 'MissingUserProvider', 'root_config' => 'config.yml']);
|
|
||||||
|
|
||||||
$client->request('GET', '/', [], [], [
|
$client->request('GET', '/', [], [], [
|
||||||
'PHP_AUTH_USER' => 'username',
|
'PHP_AUTH_USER' => 'username',
|
||||||
'PHP_AUTH_PW' => 'pa$$word',
|
'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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Symfony package.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* 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());
|
|
||||||
}
|
|
||||||
}
|
|
@ -12,6 +12,5 @@
|
|||||||
return [
|
return [
|
||||||
new Symfony\Bundle\SecurityBundle\SecurityBundle(),
|
new Symfony\Bundle\SecurityBundle\SecurityBundle(),
|
||||||
new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
|
new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
|
||||||
new Symfony\Bundle\TwigBundle\TwigBundle(),
|
|
||||||
new Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\JsonLoginBundle\JsonLoginBundle(),
|
new Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\JsonLoginBundle\JsonLoginBundle(),
|
||||||
];
|
];
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
imports:
|
imports:
|
||||||
- { resource: ./../config/default.yml }
|
- { resource: ./../config/framework.yml }
|
||||||
|
|
||||||
security:
|
security:
|
||||||
encoders:
|
encoders:
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
imports:
|
imports:
|
||||||
- { resource: ./../config/default.yml }
|
- { resource: ./../config/framework.yml }
|
||||||
|
|
||||||
security:
|
security:
|
||||||
encoders:
|
encoders:
|
||||||
|
@ -2,4 +2,4 @@
|
|||||||
twig:
|
twig:
|
||||||
debug: '%kernel.debug%'
|
debug: '%kernel.debug%'
|
||||||
strict_variables: '%kernel.debug%'
|
strict_variables: '%kernel.debug%'
|
||||||
exception_controller: Symfony\Bundle\SecurityBundle\Tests\Functional\app\ExceptionController
|
exception_controller: null # to be removed in 5.0
|
||||||
|
@ -7,8 +7,9 @@ CHANGELOG
|
|||||||
* marked the `TemplateIterator` as `internal`
|
* marked the `TemplateIterator` as `internal`
|
||||||
* added HTML comment to beginning and end of `exception_full.html.twig`
|
* added HTML comment to beginning and end of `exception_full.html.twig`
|
||||||
* added a new `TwigHtmlErrorRenderer` for `html` format, integrated with the `ErrorRenderer` component
|
* 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 `ExceptionController` and `PreviewErrorController` controllers, use `ErrorController` from the `HttpKernel` component instead
|
||||||
* deprecated default value `twig.controller.exception::showAction` of `twig.exception_controller` configuration option, set it to `null` 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
|
4.2.0
|
||||||
-----
|
-----
|
||||||
|
@ -19,7 +19,7 @@ use Twig\Environment;
|
|||||||
use Twig\Error\LoaderError;
|
use Twig\Error\LoaderError;
|
||||||
use Twig\Loader\ExistsLoaderInterface;
|
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
|
* ExceptionController renders error or exception pages for a given
|
||||||
@ -28,7 +28,7 @@ use Twig\Loader\ExistsLoaderInterface;
|
|||||||
* @author Fabien Potencier <fabien@symfony.com>
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
* @author Matthias Pigulla <mp@webfactory.de>
|
* @author Matthias Pigulla <mp@webfactory.de>
|
||||||
*
|
*
|
||||||
* @deprecated since Symfony 4.4, use the ErrorRenderer component instead.
|
* @deprecated since Symfony 4.4, use Symfony\Component\HttpKernel\Controller\ErrorController instead.
|
||||||
*/
|
*/
|
||||||
class ExceptionController
|
class ExceptionController
|
||||||
{
|
{
|
||||||
|
@ -11,39 +11,35 @@
|
|||||||
|
|
||||||
namespace Symfony\Bundle\TwigBundle\Controller;
|
namespace Symfony\Bundle\TwigBundle\Controller;
|
||||||
|
|
||||||
use Symfony\Component\ErrorRenderer\ErrorRenderer;
|
|
||||||
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
|
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
|
||||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
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.
|
* PreviewErrorController can be used to test error pages.
|
||||||
*
|
*
|
||||||
* It will create a test exception and forward it to another controller.
|
* It will create a test exception and forward it to another controller.
|
||||||
*
|
*
|
||||||
* @author Matthias Pigulla <mp@webfactory.de>
|
* @author Matthias Pigulla <mp@webfactory.de>
|
||||||
|
*
|
||||||
|
* @deprecated since Symfony 4.4, use the Symfony\Component\HttpKernel\Controller\ErrorController instead.
|
||||||
*/
|
*/
|
||||||
class PreviewErrorController
|
class PreviewErrorController
|
||||||
{
|
{
|
||||||
protected $kernel;
|
protected $kernel;
|
||||||
protected $controller;
|
protected $controller;
|
||||||
private $errorRenderer;
|
|
||||||
|
|
||||||
public function __construct(HttpKernelInterface $kernel, $controller, ErrorRenderer $errorRenderer = null)
|
public function __construct(HttpKernelInterface $kernel, $controller)
|
||||||
{
|
{
|
||||||
$this->kernel = $kernel;
|
$this->kernel = $kernel;
|
||||||
$this->controller = $controller;
|
$this->controller = $controller;
|
||||||
$this->errorRenderer = $errorRenderer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function previewErrorPageAction(Request $request, $code)
|
public function previewErrorPageAction(Request $request, $code)
|
||||||
{
|
{
|
||||||
$exception = FlattenException::createFromThrowable(new \Exception('Something has intentionally gone wrong.'), $code, ['X-Debug' => false]);
|
$exception = FlattenException::createFromThrowable(new \Exception('Something has intentionally gone wrong.'), $code);
|
||||||
|
|
||||||
if (null === $this->controller && null !== $this->errorRenderer) {
|
|
||||||
return new Response($this->errorRenderer->render($exception, $request->getPreferredFormat()), $code);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This Request mimics the parameters set by
|
* This Request mimics the parameters set by
|
||||||
|
@ -18,6 +18,8 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
|
|||||||
* Registers the Twig exception listener if Twig is registered as a templating engine.
|
* Registers the Twig exception listener if Twig is registered as a templating engine.
|
||||||
*
|
*
|
||||||
* @author Fabien Potencier <fabien@symfony.com>
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
*/
|
*/
|
||||||
class ExceptionListenerPass implements CompilerPassInterface
|
class ExceptionListenerPass implements CompilerPassInterface
|
||||||
{
|
{
|
||||||
@ -27,14 +29,19 @@ class ExceptionListenerPass implements CompilerPassInterface
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// register the exception controller only if Twig is enabled and required dependencies do exist
|
// to be removed in 5.0
|
||||||
if (!class_exists('Symfony\Component\ErrorRenderer\Exception\FlattenException') || !interface_exists('Symfony\Component\EventDispatcher\EventSubscriberInterface')) {
|
// 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');
|
$container->removeDefinition('twig.exception_listener');
|
||||||
} elseif ($container->hasParameter('templating.engines')) {
|
} else {
|
||||||
|
$container->removeDefinition('exception_listener');
|
||||||
|
|
||||||
|
if ($container->hasParameter('templating.engines')) {
|
||||||
$engines = $container->getParameter('templating.engines');
|
$engines = $container->getParameter('templating.engines');
|
||||||
if (!\in_array('twig', $engines)) {
|
if (!\in_array('twig', $engines, true)) {
|
||||||
$container->removeDefinition('twig.exception_listener');
|
$container->removeDefinition('twig.exception_listener');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,10 +36,18 @@ class Configuration implements ConfigurationInterface
|
|||||||
->children()
|
->children()
|
||||||
->scalarNode('exception_controller')
|
->scalarNode('exception_controller')
|
||||||
->defaultValue(static function () {
|
->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';
|
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()
|
||||||
->end()
|
->end()
|
||||||
;
|
;
|
||||||
|
@ -134,6 +134,7 @@
|
|||||||
<argument>%twig.exception_listener.controller%</argument>
|
<argument>%twig.exception_listener.controller%</argument>
|
||||||
<argument type="service" id="logger" on-invalid="null" />
|
<argument type="service" id="logger" on-invalid="null" />
|
||||||
<argument>%kernel.debug%</argument>
|
<argument>%kernel.debug%</argument>
|
||||||
|
<deprecated>The "%service_id%" service is deprecated since Symfony 4.4.</deprecated>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<service id="twig.controller.exception" class="Symfony\Bundle\TwigBundle\Controller\ExceptionController" public="true">
|
<service id="twig.controller.exception" class="Symfony\Bundle\TwigBundle\Controller\ExceptionController" public="true">
|
||||||
@ -145,7 +146,7 @@
|
|||||||
<service id="twig.controller.preview_error" class="Symfony\Bundle\TwigBundle\Controller\PreviewErrorController" public="true">
|
<service id="twig.controller.preview_error" class="Symfony\Bundle\TwigBundle\Controller\PreviewErrorController" public="true">
|
||||||
<argument type="service" id="http_kernel" />
|
<argument type="service" id="http_kernel" />
|
||||||
<argument>%twig.exception_listener.controller%</argument>
|
<argument>%twig.exception_listener.controller%</argument>
|
||||||
<argument type="service" id="error_renderer" on-invalid="null" />
|
<deprecated>The "%service_id%" service is deprecated since Symfony 4.4.</deprecated>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<service id="twig.configurator.environment" class="Symfony\Bundle\TwigBundle\DependencyInjection\Configurator\EnvironmentConfigurator">
|
<service id="twig.configurator.environment" class="Symfony\Bundle\TwigBundle\DependencyInjection\Configurator\EnvironmentConfigurator">
|
||||||
|
@ -18,6 +18,9 @@ use Symfony\Component\HttpFoundation\Request;
|
|||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group legacy
|
||||||
|
*/
|
||||||
class PreviewErrorControllerTest extends TestCase
|
class PreviewErrorControllerTest extends TestCase
|
||||||
{
|
{
|
||||||
public function testForwardRequestToConfiguredController()
|
public function testForwardRequestToConfiguredController()
|
||||||
|
@ -21,7 +21,7 @@ class ConfigurationTest extends TestCase
|
|||||||
{
|
{
|
||||||
$input = [
|
$input = [
|
||||||
'strict_variables' => false, // to be removed in 5.0 relying on default
|
'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'],
|
'form_themes' => ['form_div_layout.html.twig'],
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -45,14 +45,14 @@ class ConfigurationTest extends TestCase
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @group legacy
|
* @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()
|
public function testGetExceptionControllerDefault()
|
||||||
{
|
{
|
||||||
$processor = new Processor();
|
$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()
|
public function testGlobalsAreNotNormalized()
|
||||||
|
@ -4,5 +4,5 @@ $container->loadFromExtension('twig', [
|
|||||||
'autoescape_service' => 'my_project.some_bundle.template_escaping_guesser',
|
'autoescape_service' => 'my_project.some_bundle.template_escaping_guesser',
|
||||||
'autoescape_service_method' => 'guess',
|
'autoescape_service_method' => 'guess',
|
||||||
'strict_variables' => false, // to be removed in 5.0 relying on default
|
'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
|
||||||
]);
|
]);
|
||||||
|
@ -2,5 +2,5 @@
|
|||||||
|
|
||||||
$container->loadFromExtension('twig', [
|
$container->loadFromExtension('twig', [
|
||||||
'strict_variables' => false, // to be removed in 5.0 relying on default
|
'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
|
||||||
]);
|
]);
|
||||||
|
@ -12,5 +12,5 @@ $container->loadFromExtension('twig', [
|
|||||||
'thousands_separator' => '.',
|
'thousands_separator' => '.',
|
||||||
],
|
],
|
||||||
'strict_variables' => false, // to be removed in 5.0 relying on default
|
'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
|
||||||
]);
|
]);
|
||||||
|
@ -17,7 +17,7 @@ $container->loadFromExtension('twig', [
|
|||||||
'charset' => 'ISO-8859-1',
|
'charset' => 'ISO-8859-1',
|
||||||
'debug' => true,
|
'debug' => true,
|
||||||
'strict_variables' => true,
|
'strict_variables' => true,
|
||||||
'exception_controller' => null,
|
'exception_controller' => null, // to be removed in 5.0
|
||||||
'default_path' => '%kernel.project_dir%/Fixtures/templates',
|
'default_path' => '%kernel.project_dir%/Fixtures/templates',
|
||||||
'paths' => [
|
'paths' => [
|
||||||
'path1',
|
'path1',
|
||||||
|
@ -32,7 +32,7 @@ class TwigExtensionTest extends TestCase
|
|||||||
$container->registerExtension(new TwigExtension());
|
$container->registerExtension(new TwigExtension());
|
||||||
$container->loadFromExtension('twig', [
|
$container->loadFromExtension('twig', [
|
||||||
'strict_variables' => false, // to be removed in 5.0 relying on default
|
'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);
|
$this->compileContainer($container);
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ class NoTemplatingEntryKernel extends Kernel
|
|||||||
])
|
])
|
||||||
->loadFromExtension('twig', [
|
->loadFromExtension('twig', [
|
||||||
'strict_variables' => false, // to be removed in 5.0 relying on default
|
'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',
|
'default_path' => __DIR__.'/templates',
|
||||||
])
|
])
|
||||||
;
|
;
|
||||||
|
@ -13,6 +13,7 @@ CHANGELOG
|
|||||||
current directory or with a glob pattern. The fallback directories have never been advocated
|
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.
|
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`
|
* Marked all dispatched event classes as `@final`
|
||||||
|
* Added `ErrorController` to enable the preview and error rendering mechanism
|
||||||
|
|
||||||
4.3.0
|
4.3.0
|
||||||
-----
|
-----
|
||||||
|
@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* 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 <yonelceruto@gmail.com>
|
||||||
|
* @author Matthias Pigulla <mp@webfactory.de>
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
@ -16,15 +16,9 @@ use Symfony\Component\Console\ConsoleEvents;
|
|||||||
use Symfony\Component\Console\Event\ConsoleEvent;
|
use Symfony\Component\Console\Event\ConsoleEvent;
|
||||||
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
||||||
use Symfony\Component\ErrorHandler\ErrorHandler;
|
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\Event;
|
||||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
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\Debug\FileLinkFormatter;
|
||||||
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
|
|
||||||
use Symfony\Component\HttpKernel\Event\KernelEvent;
|
use Symfony\Component\HttpKernel\Event\KernelEvent;
|
||||||
use Symfony\Component\HttpKernel\KernelEvents;
|
use Symfony\Component\HttpKernel\KernelEvents;
|
||||||
|
|
||||||
@ -44,8 +38,6 @@ class DebugHandlersListener implements EventSubscriberInterface
|
|||||||
private $scream;
|
private $scream;
|
||||||
private $fileLinkFormat;
|
private $fileLinkFormat;
|
||||||
private $scope;
|
private $scope;
|
||||||
private $charset;
|
|
||||||
private $errorRenderer;
|
|
||||||
private $firstCall = true;
|
private $firstCall = true;
|
||||||
private $hasTerminatedWithException;
|
private $hasTerminatedWithException;
|
||||||
|
|
||||||
@ -57,7 +49,7 @@ class DebugHandlersListener implements EventSubscriberInterface
|
|||||||
* @param string|FileLinkFormatter|null $fileLinkFormat The format for links to source files
|
* @param string|FileLinkFormatter|null $fileLinkFormat The format for links to source files
|
||||||
* @param bool $scope Enables/disables scoping mode
|
* @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->exceptionHandler = $exceptionHandler;
|
||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
@ -66,8 +58,6 @@ class DebugHandlersListener implements EventSubscriberInterface
|
|||||||
$this->scream = $scream;
|
$this->scream = $scream;
|
||||||
$this->fileLinkFormat = $fileLinkFormat;
|
$this->fileLinkFormat = $fileLinkFormat;
|
||||||
$this->scope = $scope;
|
$this->scope = $scope;
|
||||||
$this->charset = $charset;
|
|
||||||
$this->errorRenderer = $errorRenderer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -142,33 +132,6 @@ class DebugHandlersListener implements EventSubscriberInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @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()
|
public static function getSubscribedEvents()
|
||||||
{
|
{
|
||||||
$events = [KernelEvents::REQUEST => ['configure', 2048]];
|
$events = [KernelEvents::REQUEST => ['configure', 2048]];
|
||||||
@ -177,8 +140,6 @@ class DebugHandlersListener implements EventSubscriberInterface
|
|||||||
$events[ConsoleEvents::COMMAND] = ['configure', 2048];
|
$events[ConsoleEvents::COMMAND] = ['configure', 2048];
|
||||||
}
|
}
|
||||||
|
|
||||||
$events[KernelEvents::EXCEPTION] = ['onKernelException', -2048];
|
|
||||||
|
|
||||||
return $events;
|
return $events;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,123 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* 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));
|
||||||
|
}
|
||||||
|
}
|
@ -101,7 +101,6 @@ class DebugHandlersListenerTest extends TestCase
|
|||||||
$xListeners = [
|
$xListeners = [
|
||||||
KernelEvents::REQUEST => [[$listener, 'configure']],
|
KernelEvents::REQUEST => [[$listener, 'configure']],
|
||||||
ConsoleEvents::COMMAND => [[$listener, 'configure']],
|
ConsoleEvents::COMMAND => [[$listener, 'configure']],
|
||||||
KernelEvents::EXCEPTION => [[$listener, 'onKernelException']],
|
|
||||||
];
|
];
|
||||||
$this->assertSame($xListeners, $dispatcher->getListeners());
|
$this->assertSame($xListeners, $dispatcher->getListeners());
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user