From 38493b3e4beef88be74f2510c457302e7e48f4ae Mon Sep 17 00:00:00 2001 From: Yonel Ceruto Date: Mon, 25 Nov 2019 19:08:07 -0500 Subject: [PATCH] Add preview mode support for Html and Serializer error renderers --- .../Resources/config/error_renderer.xml | 15 +++- .../Resources/config/serializer.xml | 7 ++ .../ErrorRenderer/HtmlErrorRenderer.php | 69 ++++++++++++++----- .../ErrorRenderer/SerializerErrorRenderer.php | 14 +++- .../Normalizer/ProblemNormalizer.php | 5 +- 5 files changed, 85 insertions(+), 25 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/error_renderer.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/error_renderer.xml index 84330e6b6b..4d2423feee 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/error_renderer.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/error_renderer.xml @@ -6,11 +6,22 @@ - %kernel.debug% + + + + + %kernel.debug% + + %kernel.charset% %kernel.project_dir% - + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml index f05af29eea..0dbc388ddf 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml @@ -163,6 +163,13 @@ + + + + + %kernel.debug% + + diff --git a/src/Symfony/Component/ErrorHandler/ErrorRenderer/HtmlErrorRenderer.php b/src/Symfony/Component/ErrorHandler/ErrorRenderer/HtmlErrorRenderer.php index 84cb9da424..883a94f689 100644 --- a/src/Symfony/Component/ErrorHandler/ErrorRenderer/HtmlErrorRenderer.php +++ b/src/Symfony/Component/ErrorHandler/ErrorRenderer/HtmlErrorRenderer.php @@ -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()); } diff --git a/src/Symfony/Component/ErrorHandler/ErrorRenderer/SerializerErrorRenderer.php b/src/Symfony/Component/ErrorHandler/ErrorRenderer/SerializerErrorRenderer.php index c055bc3b65..6cc363d0d9 100644 --- a/src/Symfony/Component/ErrorHandler/ErrorRenderer/SerializerErrorRenderer.php +++ b/src/Symfony/Component/ErrorHandler/ErrorRenderer/SerializerErrorRenderer.php @@ -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); } diff --git a/src/Symfony/Component/Serializer/Normalizer/ProblemNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/ProblemNormalizer.php index 0569c2923b..1b88957f76 100644 --- a/src/Symfony/Component/Serializer/Normalizer/ProblemNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/ProblemNormalizer.php @@ -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(); }