8000 [HttpKernel][Debug] Get exception content according to request format by yceruto · Pull Request #30851 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[HttpKernel][Debug] Get exception content according to request format #30851

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 1 commit 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
2 changes: 2 additions & 0 deletions src/Symfony/Component/Debug/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
-----
Expand Down
155 changes: 155 additions & 0 deletions src/Symfony/Component/Debug/ExceptionHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so a 403 error without symfony/http-foundation installed would lead to this title/message? Might be a bit confusing 😄

}

$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 .= '<exceptions>';
foreach ($exception->toArray() as $e) {
$exceptions .= sprintf('<exception class="%s" message="%s"><traces>', $e['class'], $this->escapeHtml($e['message']));
foreach ($e['trace'] as $trace) {
$exceptions .= '<trace>';
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 .= '</trace>';
}
$exceptions .= '</traces></exception>';
}
$exceptions .= '</exceptions>';
}

return <<<EOF
<?xml version="1.0" encoding="{$this->charset}" ?>
<problem xmlns="urn:ietf:rfc:7807">
<title>{$title}</title>
<status>{$statusCode}</status>
<detail>{$message}</detail>
{$exceptions}
</problem>
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()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this blocks seems repeated for all content-types? maybe move it to a private method?

$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.
*
Expand Down
24 changes: 24 additions & 0 deletions src/Symfony/Component/Debug/Tests/ExceptionHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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('<?xml version="1.0" encoding="UTF-8" ?>%A<problem xmlns="urn:ietf:rfc:7807">%A<title>Internal Server Error</title>%A<status>500</status>%A<detail>Foo</detail>%A<exceptions><exception class="RuntimeException" message="Foo"><traces><trace>%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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
];
Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Component/HttpKernel/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
0