From 259fa20ed535a770247fe86e087740b49c2aba4c Mon Sep 17 00:00:00 2001 From: Yonel Ceruto Date: Wed, 3 Apr 2019 09:05:55 -0400 Subject: [PATCH] Get exception content according to request format --- src/Symfony/Component/Debug/CHANGELOG.md | 2 + .../Component/Debug/ExceptionHandler.php | 155 ++++++++++++++++++ .../Debug/Tests/ExceptionHandlerTest.php | 24 +++ .../EventListener/ExceptionListener.php | 4 +- .../Component/HttpKernel/composer.json | 1 + 5 files changed, 184 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Debug/CHANGELOG.md b/src/Symfony/Component/Debug/CHANGELOG.md index 367e834f01e7e..a13563f61a379 100644 --- a/src/Symfony/Component/Debug/CHANGELOG.md +++ b/src/Symfony/Component/Debug/CHANGELOG.md @@ -8,6 +8,8 @@ CHANGELOG * added `Exception\FlattenException::getAsString` and `Exception\FlattenException::getTraceAsString` to increase compatibility to php exception objects +* added `ExceptionHandler::getFormattedContent()` to get the exception content +according to given format (html, json, xml, txt) 4.0.0 ----- diff --git a/src/Symfony/Component/Debug/ExceptionHandler.php b/src/Symfony/Component/Debug/ExceptionHandler.php index 18d56f3469eb7..403997557e0fb 100644 --- a/src/Symfony/Component/Debug/ExceptionHandler.php +++ b/src/Symfony/Component/Debug/ExceptionHandler.php @@ -13,6 +13,7 @@ use Symfony\Component\Debug\Exception\FlattenException; use Symfony\Component\Debug\Exception\OutOfMemoryException; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; /** @@ -189,6 +190,160 @@ public function sendPhpResponse($exception) echo $this->decorate($this->getContent($exception), $this->getStylesheet($exception)); } + /** + * Gets the content associated with the given exception. + * + * @param \Exception|FlattenException $exception An \Exception or FlattenException instance + * @param string $format The request format (html, json, xml, txt) + * + * @return string The formatted content as a string + */ + public function getFormattedContent($exception, string $format): string + { + switch ($format) { + case 'json': + return $this->getJson($exception); + case 'xml': + case 'rdf': + return $this->getXml($exception); + case 'txt': + return $this->getTxt($exception); + default: + return $this->getHtml($exception); + } + } + + /** + * Gets the JSON content associated with the given exception. + * + * @param \Exception|FlattenException $exception An \Exception or FlattenException instance + * + * @return string The JSON content as a string + */ + public function getJson($exception): string + { + if (!$exception instanceof FlattenException) { + $exception = FlattenException::create($exception); + } + + if (404 === $statusCode = $exception->getStatusCode()) { + $title = 'Not Found'; + } elseif (class_exists(Response::class) && isset(Response::$statusTexts[$statusCode])) { + $title = Response::$statusTexts[$statusCode]; + } else { + $title = 'Internal Server Error'; + } + + $content = [ + 'title' => $title, + 'status' => $statusCode, + 'detail' => $this->escapeHtml($exception->getMessage()), + ]; + + if ($this->debug) { + $content['exceptions'] = $exception->toArray(); + } + + return (string) json_encode($content); + } + + /** + * Gets the XML content associated with the given exception. + * + * @param \Exception|FlattenException $exception An \Exception or FlattenException instance + * + * @return string The XML content as a string + */ + public function getXml($exception): string + { + if (!$exception instanceof FlattenException) { + $exception = FlattenException::create($exception); + } + + if (404 === $statusCode = $exception->getStatusCode()) { + $title = 'Not Found'; + } elseif (class_exists(Response::class) && isset(Response::$statusTexts[$statusCode])) { + $title = Response::$statusTexts[$statusCode]; + } else { + $title = 'Internal Server Error'; + } + $message = $this->escapeHtml($exception->getMessage()); + + $exceptions = ''; + if ($this->debug) { + $exceptions .= ''; + foreach ($exception->toArray() as $e) { + $exceptions .= sprintf('', $e['class'], $this->escapeHtml($e['message'])); + foreach ($e['trace'] as $trace) { + $exceptions .= ''; + if ($trace['function']) { + $exceptions .= sprintf('at %s%s%s(%s) ', $trace['class'], $trace['type'], $trace['function'], strip_tags($this->formatArgs($trace['args']))); + } + if (isset($trace['file'], $trace['line'])) { + $exceptions .= strip_tags($this->formatPath($trace['file'], $trace['line'])); + } + $exceptions .= ''; + } + $exceptions .= ''; + } + $exceptions .= ''; + } + + return << + + {$title} + {$statusCode} + {$message} + {$exceptions} + +EOF; + } + + /** + * Gets the TXT content associated with the given exception. + * + * @param \Exception|FlattenException $exception An \Exception or FlattenException instance + * + * @return string The TXT content as a string + */ + public function getTxt($exception): string + { + if (!$exception instanceof FlattenException) { + $exception = FlattenException::create($exception); + } + + if (404 === $statusCode = $exception->getStatusCode()) { + $title = 'Not Found'; + } elseif (class_exists(Response::class) && isset(Response::$statusTexts[$statusCode])) { + $title = Response::$statusTexts[$statusCode]; + } else { + $title = 'Internal Server Error'; + } + + $content = sprintf("[title] %s\n", $title); + $content .= sprintf("[status] %s\n", $statusCode); + $content .= sprintf("[detail] %s\n", $exception->getMessage()); + + if ($this->debug) { + foreach ($exception->toArray() as $i => $e) { + $content .= sprintf("[%d] %s: %s\n", $i + 1, $e['class'], $e['message']); + + foreach ($e['trace'] as $trace) { + if ($trace['function']) { + $content .= sprintf('at %s%s%s(%s) ', $trace['class'], $trace['type'], $trace['function'], strip_tags($this->formatArgs($trace['args']))); + } + if (isset($trace['file'], $trace['line'])) { + $content .= strip_tags($this->formatPath($trace['file'], $trace['line'])); + } + $content .= "\n"; + } + } + } + + return $content; + } + /** * Gets the full HTML content associated with the given exception. * diff --git a/src/Symfony/Component/Debug/Tests/ExceptionHandlerTest.php b/src/Symfony/Component/Debug/Tests/ExceptionHandlerTest.php index 4910fe5ec96be..955ecdec0717f 100644 --- a/src/Symfony/Component/Debug/Tests/ExceptionHandlerTest.php +++ b/src/Symfony/Component/Debug/Tests/ExceptionHandlerTest.php @@ -139,4 +139,28 @@ public function testHandleOutOfMemoryException() $handler->handle($exception); } + + public function testJsonExceptionContent() + { + $handler = new ExceptionHandler(true); + $content = $handler->getJson(new \RuntimeException('Foo')); + + $this->assertStringMatchesFormat('{"title":"Internal Server Error","status":500,"detail":"Foo","exceptions":[{"message":"Foo","class":"RuntimeException"%S}]}', $content); + } + + public function testXmlExceptionContent() + { + $handler = new ExceptionHandler(true); + $content = $handler->getXml(new \RuntimeException('Foo')); + + $this->assertStringMatchesFormat('%A%AInternal Server Error%A500%AFoo%A%A', $content); + } + + public function testTxtExceptionContent() + { + $handler = new ExceptionHandler(true); + $content = $handler->getTxt(new \RuntimeException('Foo')); + + $this->assertStringMatchesFormat("[title] Internal Server Error\n[status] 500\n[detail] Foo\n[1] RuntimeException: Foo\nin ExceptionHandlerTest.php line %A", $content); + } } diff --git a/src/Symfony/Component/HttpKernel/EventListener/ExceptionListener.php b/src/Symfony/Component/HttpKernel/EventListener/ExceptionListener.php index 8e31ecc944e37..e32532cf0bdc4 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/ExceptionListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/ExceptionListener.php @@ -151,10 +151,10 @@ protected function duplicateRequest(\Exception $exception, Request $request) { $attributes = [ 'exception' => $exception = FlattenException::create($exception), - '_controller' => $this->controller ?: function () use ($exception) { + '_controller' => $this->controller ?: function () use ($exception, $request) { $handler = new ExceptionHandler($this->debug, $this->charset, $this->fileLinkFormat); - return new Response($handler->getHtml($exception), $exception->getStatusCode(), $exception->getHeaders()); + return new Response($handler->getFormattedContent($exception, $request->getRequestFormat()), $exception->getStatusCode(), $exception->getHeaders()); }, 'logger' => $this->logger instanceof DebugLoggerInterface ? $this->logger : null, ]; diff --git a/src/Symfony/Component/HttpKernel/composer.json b/src/Symfony/Component/HttpKernel/composer.json index ca818d16707ab..25ce022c749ef 100644 --- a/src/Symfony/Component/HttpKernel/composer.json +++ b/src/Symfony/Component/HttpKernel/composer.json @@ -48,6 +48,7 @@ "conflict": { "symfony/browser-kit": "<4.3", "symfony/config": "<3.4", + "symfony/debug": "<4.3", "symfony/dependency-injection": "<4.2", "symfony/translation": "<4.2", "symfony/var-dumper": "<4.1.1",