Skip to content

Commit

Permalink
add error rendering as JSON
Browse files Browse the repository at this point in the history
  • Loading branch information
klimov-paul committed Apr 23, 2024
1 parent a90fee4 commit 0ec7667
Show file tree
Hide file tree
Showing 2 changed files with 217 additions and 0 deletions.
120 changes: 120 additions & 0 deletions src/ErrorHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,124 @@ protected function setExceptionTrace(\Exception $exception, array $trace): void
$traceReflection->setAccessible(true);
$traceReflection->setValue($exception, $trace);
}

public function shouldRenderErrorAsJson(): bool
{
return !empty($_SERVER['HTTP_ACCEPT']) && strcasecmp($_SERVER['HTTP_ACCEPT'], 'application/json') === 0;
}

/**
* Renders current error information as JSON output.
* This method will display information from current {@see getError()} value.
*
* > Note: this method does NOT terminate the script.
*/
public function renderErrorAsJson(): void
{
$error = $this->getError();
if (empty($error)) {
return;
}

unset($error['trace']);

$responseData = [
'error' => $this->getHttpHeader($error['code']),
'code' => $error['code'],
];

$jsonFlags = 0;
if (YII_DEBUG) {
$jsonFlags = JSON_PRETTY_PRINT;

$error['traces'] = $this->filterErrorTrace($error['traces']);

$responseData = array_merge($responseData, $error);
}

header('Content-Type: application/json; charset=utf-8');

echo json_encode($responseData, $jsonFlags);
}

/**
* @param array $trace raw exception stack trace.
* @return array simplified stack trace.
*/
private function filterErrorTrace(array $trace): array
{
/** @var ErrorTraceFilter $traceFilter */
$traceFilter = Yii::createComponent([
'class' => ErrorTraceFilter::class,
]);

return $traceFilter->filter($trace);
}

/**
* {@inheritDoc}
*/
protected function renderException(): void
{
$exception = $this->getException();

if ($this->errorAction !== null) {
Yii::app()->runController($this->errorAction);

return;
}

if ($exception instanceof \CHttpException || !YII_DEBUG) {
$this->renderError();

return;
}

if ($this->shouldRenderErrorAsJson()) {
$this->renderErrorAsJson();

return;
}

if ($this->isAjaxRequest()) {
Yii::app()->displayException($exception);

return;
}

$this->render('exception', $this->getError());
}

/**
* {@inheritDoc}
*/
protected function renderError(): void
{
if ($this->errorAction !== null) {
Yii::app()->runController($this->errorAction);

return;
}

if ($this->shouldRenderErrorAsJson()) {
$this->renderErrorAsJson();

return;
}

$data = $this->getError();
if ($this->isAjaxRequest()) {
Yii::app()->displayError($data['code'], $data['message'], $data['file'], $data['line']);

return;
}

if (YII_DEBUG) {
$this->render('exception', $data);

return;
}

$this->render('error',$data);
}
}
97 changes: 97 additions & 0 deletions src/ErrorTraceFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php

namespace yii1tech\error\handler;

use CComponent;

/**
* @author Paul Klimov <[email protected]>
* @since 1.0
*/
class ErrorTraceFilter extends CComponent
{
/**
* @var int maximum number of trace source code lines to be displayed. Defaults to 10.
*/
public $maxTraceSourceLines = 10;

public function filter(array $trace): array
{
$trace = array_slice($trace, 0, $this->maxTraceSourceLines);

$result = [];
foreach ($trace as $entry) {
if (array_key_exists('args', $entry)) {
$entry['args'] = $this->simplifyArguments($entry['args']);
}

$result[] = $entry;
}

return $result;
}

/**
* Converts arguments array to their simplified representation.
*
* @param array $args arguments array to be converted.
* @return string string representation of the arguments array.
*/
private function simplifyArguments(array $args) : string
{
$count = 0;

$isAssoc = $args !== array_values($args);

foreach ($args as $key => $value) {
$count++;

if ($count >= 5) {
if ($count > 5) {
unset($args[$key]);
} else {
$args[$key] = '...';
}

continue;
}

$args[$key] = $this->simplifyArgument($value);

if (is_string($key)) {
$args[$key] = "'" . $key . "' => " . $args[$key];
} elseif ($isAssoc) {
$args[$key] = $key.' => '.$args[$key];
}
}

return implode(', ', $args);
}

/**
* @param mixed $value
* @return mixed
*/
private function simplifyArgument($value)
{
if (is_object($value)) {
return get_class($value);
} elseif (is_bool($value)) {
return $value ? 'true' : 'false';
} elseif (is_string($value)) {
if (strlen($value) > 64) {
return "'" . substr($value, 0, 64) . "...'";
} else {
return "'" . $value . "'";
}
} elseif (is_array($value)) {
return '[' . $this->simplifyArguments($value) . ']';
} elseif ($value === null) {
return 'null';
} elseif (is_resource($value)) {
return 'resource';
}

return $value;
}
}

0 comments on commit 0ec7667

Please sign in to comment.