diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 22517ad5c7d3f..9d80f497b2c89 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -452,6 +452,7 @@ public function load(array $configs, ContainerBuilder $container) $this->registerSecretsConfiguration($config['secrets'], $container, $loader); $container->getDefinition('exception_listener')->replaceArgument(3, $config['exceptions']); + $container->getDefinition('error_handler.error_renderer.html')->replaceArgument(6, $config['exceptions']); if ($this->isConfigEnabled($container, $config['serializer'])) { if (!class_exists(\Symfony\Component\Serializer\Serializer::class)) { @@ -459,6 +460,8 @@ public function load(array $configs, ContainerBuilder $container) } $this->registerSerializerConfiguration($config['serializer'], $container, $loader); + + $container->getDefinition('error_handler.error_renderer.serializer')->replaceArgument(4, $config['exceptions']); } if ($propertyInfoEnabled) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/error_renderer.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/error_renderer.php index 67f28ce44d838..abc0c67e60cff 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/error_renderer.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/error_renderer.php @@ -30,6 +30,7 @@ ->factory([HtmlErrorRenderer::class, 'getAndCleanOutputBuffer']) ->args([service('request_stack')]), service('logger')->nullOnInvalid(), + abstract_arg('Configuration per exception class'), ]) ->alias('error_renderer.html', 'error_handler.error_renderer.html') diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php index dfb2589cba315..0176f8677b75f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php @@ -208,6 +208,7 @@ inline_service() ->factory([HtmlErrorRenderer::class, 'isDebug']) ->args([service('request_stack'), param('kernel.debug')]), + abstract_arg('Configuration per exception class'), ]) ; diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php index 00b8d8aafbd5a..0b96a819e3e51 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php @@ -106,7 +106,7 @@ param('kernel.error_controller'), service('logger')->nullOnInvalid(), param('kernel.debug'), - abstract_arg('an exceptions to log & status code mapping'), + abstract_arg('Configuration per exception class'), ]) ->tag('kernel.event_subscriber') ->tag('monolog.logger', ['channel' => 'request']) diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 71e4940ff0a4b..eb57788da90cc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -23,7 +23,7 @@ "symfony/dependency-injection": "^5.3|^6.0", "symfony/deprecation-contracts": "^2.1|^3", "symfony/event-dispatcher": "^5.1|^6.0", - "symfony/error-handler": "^4.4.1|^5.0.1|^6.0", + "symfony/error-handler": "^5.4.2|^6.0.2", "symfony/http-foundation": "^5.3|^6.0", "symfony/http-kernel": "^5.4|^6.0", "symfony/polyfill-mbstring": "~1.0", diff --git a/src/Symfony/Component/ErrorHandler/ErrorRenderer/HtmlErrorRenderer.php b/src/Symfony/Component/ErrorHandler/ErrorRenderer/HtmlErrorRenderer.php index 6f12185a068ca..a12149f369579 100644 --- a/src/Symfony/Component/ErrorHandler/ErrorRenderer/HtmlErrorRenderer.php +++ b/src/Symfony/Component/ErrorHandler/ErrorRenderer/HtmlErrorRenderer.php @@ -39,15 +39,17 @@ class HtmlErrorRenderer implements ErrorRendererInterface private $projectDir; private $outputBuffer; private $logger; + private $exceptionsMapping; private static $template = 'views/error.html.php'; /** - * @param bool|callable $debug The debugging mode as a boolean or a callable that should return it + * @param bool|callable $debug The debugging mode as a boolean or a callable that should return it * @param string|FileLinkFormatter|null $fileLinkFormat - * @param bool|callable $outputBuffer The output buffer as a string or a callable that should return it + * @param bool|callable $outputBuffer The output buffer as a string or a callable that should return it + * @param array $exceptionsMapping Configuration per exception class */ - public function __construct($debug = false, string $charset = null, $fileLinkFormat = null, string $projectDir = null, $outputBuffer = '', LoggerInterface $logger = null) + public function __construct($debug = false, string $charset = null, $fileLinkFormat = null, string $projectDir = null, $outputBuffer = '', LoggerInterface $logger = null, array $exceptionsMapping = []) { if (!\is_bool($debug) && !\is_callable($debug)) { throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be a boolean or a callable, "%s" given.', __METHOD__, \gettype($debug))); @@ -63,6 +65,7 @@ public function __construct($debug = false, string $charset = null, $fileLinkFor $this->projectDir = $projectDir; $this->outputBuffer = $outputBuffer; $this->logger = $logger; + $this->exceptionsMapping = $exceptionsMapping; } /** @@ -76,7 +79,14 @@ public function render(\Throwable $exception): FlattenException $headers['X-Debug-Exception-File'] = rawurlencode($exception->getFile()).':'.$exception->getLine(); } - $exception = FlattenException::createFromThrowable($exception, null, $headers); + $statusCode = 500; + foreach ($this->exceptionsMapping as $class => $config) { + if ($exception instanceof $class && $config['status_code']) { + $statusCode = $config['status_code']; + break; + } + } + $exception = FlattenException::createFromThrowable($exception, $statusCode, $headers); return $exception->setAsString($this->renderException($exception)); } @@ -262,8 +272,6 @@ private function formatFile(string $file, int $line, string $text = null): strin * @param string $file A file path * @param int $line The selected line number * @param int $srcContext The number of displayed lines around or -1 for the whole file - * - * @return string */ private function fileExcerpt(string $file, int $line, int $srcContext = 3): string { diff --git a/src/Symfony/Component/ErrorHandler/ErrorRenderer/SerializerErrorRenderer.php b/src/Symfony/Component/ErrorHandler/ErrorRenderer/SerializerErrorRenderer.php index cec8e4d413dce..f49bd3aec94cd 100644 --- a/src/Symfony/Component/ErrorHandler/ErrorRenderer/SerializerErrorRenderer.php +++ b/src/Symfony/Component/ErrorHandler/ErrorRenderer/SerializerErrorRenderer.php @@ -28,13 +28,15 @@ class SerializerErrorRenderer implements ErrorRendererInterface private $format; private $fallbackErrorRenderer; private $debug; + private $exceptionsMapping; /** - * @param string|callable(FlattenException) $format The format as a string or a callable that should return it - * formats not supported by Request::getMimeTypes() should be given as mime types - * @param bool|callable $debug The debugging mode as a boolean or a callable that should return it + * @param string|callable(FlattenException) $format The format as a string or a callable that should return it + * formats not supported by Request::getMimeTypes() should be given as mime types + * @param bool|callable $debug The debugging mode as a boolean or a callable that should return it + * @param array $exceptionsMapping Configuration per exception class */ - public function __construct(SerializerInterface $serializer, $format, ErrorRendererInterface $fallbackErrorRenderer = null, $debug = false) + public function __construct(SerializerInterface $serializer, $format, ErrorRendererInterface $fallbackErrorRenderer = null, $debug = false, array $exceptionsMapping = []) { if (!\is_string($format) && !\is_callable($format)) { throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be a string or a callable, "%s" given.', __METHOD__, \gettype($format))); @@ -48,6 +50,7 @@ public function __construct(SerializerInterface $serializer, $format, ErrorRende $this->format = $format; $this->fallbackErrorRenderer = $fallbackErrorRenderer ?? new HtmlErrorRenderer(); $this->debug = $debug; + $this->exceptionsMapping = $exceptionsMapping; } /** @@ -62,7 +65,15 @@ public function render(\Throwable $exception): FlattenException $headers['X-Debug-Exception-File'] = rawurlencode($exception->getFile()).':'.$exception->getLine(); } - $flattenException = FlattenException::createFromThrowable($exception, null, $headers); + $statusCode = 500; + foreach ($this->exceptionsMapping as $class => $config) { + if ($exception instanceof $class && $config['status_code']) { + $statusCode = $config['status_code']; + break; + } + } + + $flattenException = FlattenException::createFromThrowable($exception, $statusCode, $headers); try { $format = \is_string($this->format) ? $this->format : ($this->format)($flattenException); diff --git a/src/Symfony/Component/ErrorHandler/Tests/ErrorRenderer/HtmlErrorRendererTest.php b/src/Symfony/Component/ErrorHandler/Tests/ErrorRenderer/HtmlErrorRendererTest.php index f292d0f79618f..34ed98faa47b6 100644 --- a/src/Symfony/Component/ErrorHandler/Tests/ErrorRenderer/HtmlErrorRendererTest.php +++ b/src/Symfony/Component/ErrorHandler/Tests/ErrorRenderer/HtmlErrorRendererTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; +use Symfony\Component\HttpFoundation\Response; class HtmlErrorRendererTest extends TestCase { @@ -40,6 +41,22 @@ public function getRenderData(): iterable %A