Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[ErrorHandler] Render template with status code assigned to the exception #44456

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -452,13 +452,16 @@ 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)) {
throw new LogicException('Serializer support cannot be enabled as the Serializer component is not installed. Try running "composer require symfony/serializer-pack".');
}

$this->registerSerializerConfiguration($config['serializer'], $container, $loader);

$container->getDefinition('error_handler.error_renderer.serializer')->replaceArgument(4, $config['exceptions']);
}

if ($propertyInfoEnabled) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@
inline_service()
->factory([HtmlErrorRenderer::class, 'isDebug'])
->args([service('request_stack'), param('kernel.debug')]),
abstract_arg('Configuration per exception class'),
])
;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'])
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Bundle/FrameworkBundle/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)));
Expand All @@ -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;
}

/**
Expand All @@ -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));
}
Expand Down Expand Up @@ -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
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)));
Expand All @@ -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;
}

/**
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use PHPUnit\Framework\TestCase;
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
use Symfony\Component\HttpFoundation\Response;

class HtmlErrorRendererTest extends TestCase
{
Expand Down Expand Up @@ -40,6 +41,22 @@ public function getRenderData(): iterable
<html>
%A<title>An Error Occurred: Internal Server Error</title>
%A<h2>The server returned a "500 Internal Server Error".</h2>%A
HTML;

$expectedDebugWithStatusCode = <<<HTML
<!-- Foo (418 I'm a teapot) -->
<!DOCTYPE html>
<html lang="en">
%A<title>Foo (418 I'm a teapot)</title>
%A<div class="trace trace-as-html" id="trace-box-1">%A
<!-- Foo (418 I'm a teapot) -->
HTML;

$expectedNonDebugWithStatusCode = <<<HTML
<!DOCTYPE html>
<html>
%A<title>An Error Occurred: I'm a teapot</title>
%A<h2>The server returned a "418 I'm a teapot".</h2>%A
HTML;

yield '->render() returns the HTML content WITH stack traces in debug mode' => [
Expand All @@ -53,5 +70,27 @@ public function getRenderData(): iterable
new HtmlErrorRenderer(false),
$expectedNonDebug,
];

yield '->render() returns the HTML content WITH stack traces in debug mode and contains the correct status code' => [
new \RuntimeException('Foo'),
new HtmlErrorRenderer(true, null, null, null, '', null, [
\RuntimeException::class => [
'status_code' => Response::HTTP_I_AM_A_TEAPOT,
'log_level' => null,
],
]),
$expectedDebugWithStatusCode,
];

yield '->render() returns the HTML content WITHOUT stack traces in non-debug mode and contains the correct status code' => [
new \RuntimeException('Foo'),
new HtmlErrorRenderer(false, null, null, null, '', null, [
\RuntimeException::class => [
'status_code' => Response::HTTP_I_AM_A_TEAPOT,
'log_level' => null,
],
]),
$expectedNonDebugWithStatusCode,
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Serializer\Tests\ErrorRenderer;

use function json_decode;
use PHPUnit\Framework\TestCase;
use Symfony\Component\ErrorHandler\ErrorRenderer\SerializerErrorRenderer;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;

/**
* @author Alexey Deriyenko <[email protected]>
*/
class SerializerErrorRendererTest extends TestCase
{
/**
* @dataProvider getRenderData
*/
public function testRenderReturnsJson(\Throwable $exception, SerializerErrorRenderer $serializerErrorRenderer)
{
$this->assertJson($serializerErrorRenderer->render($exception)->getAsString());
}

/**
* @dataProvider getRenderData
*/
public function testRenderReturnsJsonWithCorrectStatusCode(\Throwable $exception, SerializerErrorRenderer $serializerErrorRenderer, int $expectedStatusCode)
{
$statusCodeFromJson = json_decode($serializerErrorRenderer->render($exception)->getAsString())->statusCode;
$this->assertEquals($expectedStatusCode, $statusCodeFromJson);
}

/**
* @dataProvider getRenderData
*/
public function testRenderReturnsJsonWithCorrectStatusText(\Throwable $exception, SerializerErrorRenderer $serializerErrorRenderer, int $expectedStatusCode, string $expectedStatusText)
{
$statusTextFromJson = json_decode($serializerErrorRenderer->render($exception)->getAsString())->statusText;
$this->assertEquals($expectedStatusText, $statusTextFromJson);
}

public function getRenderData(): iterable
{
yield '->render() returns the JSON content without exception mapping config' => [
new \RuntimeException('Foo'),
new SerializerErrorRenderer(new Serializer([new ObjectNormalizer()], [new JsonEncoder()]), 'json'),
Response::HTTP_INTERNAL_SERVER_ERROR,
Response::$statusTexts[Response::HTTP_INTERNAL_SERVER_ERROR],
];

yield '->render() returns the JSON content with exception mapping config' => [
new \RuntimeException('Foo'),
new SerializerErrorRenderer(new Serializer([new ObjectNormalizer()], [new JsonEncoder()]), 'json', null, false, [
\RuntimeException::class => [
'status_code' => Response::HTTP_I_AM_A_TEAPOT,
'log_level' => null,
],
]),
Response::HTTP_I_AM_A_TEAPOT,
Response::$statusTexts[Response::HTTP_I_AM_A_TEAPOT],
];
}
}