Add preview mode support for Html and Serializer error renderers

This commit is contained in:
Yonel Ceruto 2019-11-25 19:08:07 -05:00 committed by Nicolas Grekas
parent ac20382f41
commit 38493b3e4b
5 changed files with 85 additions and 25 deletions

View File

@ -6,11 +6,22 @@
<services>
<service id="error_handler.error_renderer.html" class="Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer">
<argument>%kernel.debug%</argument>
<argument type="service">
<service>
<factory class="Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer" method="isDebug" />
<argument type="service" id="request_stack" />
<argument>%kernel.debug%</argument>
</service>
</argument>
<argument>%kernel.charset%</argument>
<argument type="service" id="debug.file_link_formatter" on-invalid="null" />
<argument>%kernel.project_dir%</argument>
<argument type="service" id="request_stack" />
<argument type="service">
<service>
<factory class="Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer" method="getAndCleanOutputBuffer" />
<argument type="service" id="request_stack" />
</service>
</argument>
<argument type="service" id="logger" on-invalid="null" />
</service>

View File

@ -163,6 +163,13 @@
</service>
</argument>
<argument type="service" id="error_renderer.html" />
<argument type="service">
<service>
<factory class="Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer" method="isDebug" />
<argument type="service" id="request_stack" />
<argument>%kernel.debug%</argument>
</service>
</argument>
</service>
</services>
</container>

View File

@ -36,16 +36,28 @@ class HtmlErrorRenderer implements ErrorRendererInterface
private $charset;
private $fileLinkFormat;
private $projectDir;
private $requestStack;
private $outputBuffer;
private $logger;
public function __construct(bool $debug = false, string $charset = null, $fileLinkFormat = null, string $projectDir = null, RequestStack $requestStack = null, LoggerInterface $logger = null)
/**
* @param bool|callable $debug The debugging mode as a boolean or a callable that should return it
* @param bool|callable $outputBuffer The output buffer as a string or a callable that should return it
*/
public function __construct($debug = false, string $charset = null, $fileLinkFormat = null, string $projectDir = null, $outputBuffer = '', LoggerInterface $logger = null)
{
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__, \is_object($debug) ? \get_class($debug) : \gettype($debug)));
}
if (!\is_string($outputBuffer) && !\is_callable($outputBuffer)) {
throw new \TypeError(sprintf('Argument 5 passed to %s() must be a string or a callable, %s given.', __METHOD__, \is_object($outputBuffer) ? \get_class($outputBuffer) : \gettype($outputBuffer)));
}
$this->debug = $debug;
$this->charset = $charset ?: (ini_get('default_charset') ?: 'UTF-8');
$this->fileLinkFormat = $fileLinkFormat ?: (ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'));
$this->projectDir = $projectDir;
$this->requestStack = $requestStack;
$this->outputBuffer = $outputBuffer;
$this->logger = $logger;
}
@ -57,7 +69,7 @@ class HtmlErrorRenderer implements ErrorRendererInterface
$exception = FlattenException::createFromThrowable($exception, null, [
'Content-Type' => 'text/html; charset='.$this->charset,
]);
return $exception->setAsString($this->renderException($exception));
}
@ -81,12 +93,43 @@ class HtmlErrorRenderer implements ErrorRendererInterface
return $this->include('assets/css/exception.css');
}
public static function isDebug(RequestStack $requestStack, bool $debug): \Closure
{
return static function () use ($requestStack, $debug): bool {
if (!$request = $requestStack->getCurrentRequest()) {
return $debug;
}
return $debug && $request->attributes->getBoolean('showException', true);
};
}
public static function getAndCleanOutputBuffer(RequestStack $requestStack): \Closure
{
return static function () use ($requestStack): string {
if (!$request = $requestStack->getCurrentRequest()) {
return '';
}
$startObLevel = $request->headers->get('X-Php-Ob-Level', -1);
if (ob_get_level() <= $startObLevel) {
return '';
}
Response::closeOutputBuffers($startObLevel + 1, true);
return ob_get_clean();
};
}
private function renderException(FlattenException $exception, string $debugTemplate = 'views/exception_full.html.php'): string
{
$debug = \is_bool($this->debug) ? $this->debug : ($this->debug)($exception);
$statusText = $this->escape($exception->getStatusText());
$statusCode = $this->escape($exception->getStatusCode());
if (!$this->debug) {
if (!$debug) {
return $this->include('views/error.html.php', [
'statusText' => $statusText,
'statusCode' => $statusCode,
@ -94,7 +137,6 @@ class HtmlErrorRenderer implements ErrorRendererInterface
}
$exceptionMessage = $this->escape($exception->getMessage());
$request = $this->requestStack ? $this->requestStack->getCurrentRequest() : null;
return $this->include($debugTemplate, [
'exception' => $exception,
@ -102,21 +144,10 @@ class HtmlErrorRenderer implements ErrorRendererInterface
'statusText' => $statusText,
'statusCode' => $statusCode,
'logger' => $this->logger instanceof DebugLoggerInterface ? $this->logger : null,
'currentContent' => $request ? $this->getAndCleanOutputBuffering($request->headers->get('X-Php-Ob-Level', -1)) : '',
'currentContent' => \is_string($this->outputBuffer) ? $this->outputBuffer : ($this->outputBuffer)(),
]);
}
private function getAndCleanOutputBuffering(int $startObLevel): string
{
if (ob_get_level() <= $startObLevel) {
return '';
}
Response::closeOutputBuffers($startObLevel + 1, true);
return ob_get_clean();
}
/**
* Formats an array as a string.
*/
@ -312,7 +343,7 @@ class HtmlErrorRenderer implements ErrorRendererInterface
{
extract($context, EXTR_SKIP);
ob_start();
include __DIR__ . '/../Resources/' .$name;
include __DIR__.'/../Resources/'.$name;
return trim(ob_get_clean());
}

View File

@ -26,19 +26,26 @@ class SerializerErrorRenderer implements ErrorRendererInterface
private $serializer;
private $format;
private $fallbackErrorRenderer;
private $debug;
/**
* @param string|callable(FlattenException) $format The format as a string or a callable that should return it
* @param bool|callable $debug The debugging mode as a boolean or a callable that should return it
*/
public function __construct(SerializerInterface $serializer, $format, ErrorRendererInterface $fallbackErrorRenderer = null)
public function __construct(SerializerInterface $serializer, $format, ErrorRendererInterface $fallbackErrorRenderer = null, $debug = false)
{
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__, \is_object($format) ? \get_class($format) : \gettype($format)));
}
if (!\is_bool($debug) && !\is_callable($debug)) {
throw new \TypeError(sprintf('Argument 4 passed to %s() must be a boolean or a callable, %s given.', __METHOD__, \is_object($debug) ? \get_class($debug) : \gettype($debug)));
}
$this->serializer = $serializer;
$this->format = $format;
$this->fallbackErrorRenderer = $fallbackErrorRenderer ?? new HtmlErrorRenderer();
$this->debug = $debug;
}
/**
@ -51,7 +58,10 @@ class SerializerErrorRenderer implements ErrorRendererInterface
try {
$format = \is_string($this->format) ? $this->format : ($this->format)($flattenException);
return $flattenException->setAsString($this->serializer->serialize($flattenException, $format, ['exception' => $exception]));
return $flattenException->setAsString($this->serializer->serialize($flattenException, $format, [
'exception' => $exception,
'debug' => \is_bool($this->debug) ? $this->debug : ($this->debug)($exception),
]));
} catch (NotEncodableValueException $e) {
return $this->fallbackErrorRenderer->render($exception);
}

View File

@ -41,14 +41,15 @@ class ProblemNormalizer implements NormalizerInterface, CacheableSupportsMethodI
public function normalize($exception, $format = null, array $context = [])
{
$context += $this->defaultContext;
$debug = $this->debug && $context['debug'] ?? true;
$data = [
'type' => $context['type'],
'title' => $context['title'],
'status' => $context['status'] ?? $exception->getStatusCode(),
'detail' => $this->debug ? $exception->getMessage() : $exception->getStatusText(),
'detail' => $debug ? $exception->getMessage() : $exception->getStatusText(),
];
if ($this->debug) {
if ($debug) {
$data['class'] = $exception->getClass();
$data['trace'] = $exception->getTrace();
}