-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
0ec7667
commit ef3aee2
Showing
4 changed files
with
184 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,13 @@ | |
use Yii; | ||
|
||
/** | ||
* ErrorHandler is an enhanced version of standard Yii error handler. | ||
* | ||
* Its main feature is conversion of the PHP errors into exceptions, so they may be processed via `try..catch` blocks. | ||
* | ||
* > Note: in order for error to exception conversion to work, the error handler component should be added to the | ||
* application "preload" section. | ||
* | ||
* Application configuration example: | ||
* | ||
* ``` | ||
|
@@ -25,17 +32,35 @@ | |
* ] | ||
* ``` | ||
* | ||
* In addition, this class provides support for error/exception rendering as JSON output, which is useful for modern | ||
* XHR and API implementation. | ||
* | ||
* @author Paul Klimov <[email protected]> | ||
* @since 1.0 | ||
*/ | ||
class ErrorHandler extends CErrorHandler | ||
{ | ||
/** | ||
* @var bool whether to convert PHP Errors into Exceptions. | ||
* | ||
* @see \ErrorException | ||
*/ | ||
public $convertErrorToException = true; | ||
|
||
/** | ||
* @var callable|null a PHP callback, which result should determine whether the error/exception should be displayed as JSON. | ||
* The callback signature: | ||
* | ||
* ``` | ||
* function(): bool | ||
* ``` | ||
* | ||
* If not set default condition of matching 'Accept' HTTP request header will be used. | ||
* | ||
* @see shouldRenderErrorAsJson() | ||
*/ | ||
public $shouldRenderErrorAsJsonCallback; | ||
|
||
/** | ||
* {@inheritDoc} | ||
*/ | ||
|
@@ -109,8 +134,21 @@ protected function setExceptionTrace(\Exception $exception, array $trace): void | |
$traceReflection->setValue($exception, $trace); | ||
} | ||
|
||
/** | ||
* Checks if error/exception should be rendered as JSON. | ||
* | ||
* @see $shouldRenderErrorAsJsonCallback | ||
* | ||
* > Tip: you can invoke this method inside your custom handler specified via {@see $errorAction}. | ||
* | ||
* @return bool whether the error/exception should be rendered as JSON. | ||
*/ | ||
public function shouldRenderErrorAsJson(): bool | ||
{ | ||
if ($this->shouldRenderErrorAsJsonCallback !== null) { | ||
return call_user_func($this->shouldRenderErrorAsJsonCallback); | ||
} | ||
|
||
return !empty($_SERVER['HTTP_ACCEPT']) && strcasecmp($_SERVER['HTTP_ACCEPT'], 'application/json') === 0; | ||
} | ||
|
||
|
@@ -119,6 +157,8 @@ public function shouldRenderErrorAsJson(): bool | |
* This method will display information from current {@see getError()} value. | ||
* | ||
* > Note: this method does NOT terminate the script. | ||
* | ||
* > Tip: you can invoke this method inside your custom handler specified via {@see $errorAction}. | ||
*/ | ||
public function renderErrorAsJson(): void | ||
{ | ||
|
@@ -154,10 +194,8 @@ public function renderErrorAsJson(): void | |
*/ | ||
private function filterErrorTrace(array $trace): array | ||
{ | ||
/** @var ErrorTraceFilter $traceFilter */ | ||
$traceFilter = Yii::createComponent([ | ||
'class' => ErrorTraceFilter::class, | ||
]); | ||
$traceFilter = new ErrorTraceFilter(); | ||
$traceFilter->maxTraceSize = $this->maxTraceSourceLines; | ||
|
||
return $traceFilter->filter($trace); | ||
} | ||
|
@@ -169,12 +207,6 @@ 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(); | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,10 @@ | |
use CComponent; | ||
|
||
/** | ||
* ErrorTraceFilter creates simplified representation of the stack trace. | ||
* | ||
* @see \yii1tech\error\handler\ErrorHandler::filterErrorTrace() | ||
* | ||
* @author Paul Klimov <[email protected]> | ||
* @since 1.0 | ||
*/ | ||
|
@@ -13,11 +17,17 @@ class ErrorTraceFilter extends CComponent | |
/** | ||
* @var int maximum number of trace source code lines to be displayed. Defaults to 10. | ||
*/ | ||
public $maxTraceSourceLines = 10; | ||
public $maxTraceSize = 10; | ||
|
||
/** | ||
* Creates simplified representation of the given stack trace. | ||
* | ||
* @param array $trace raw trace. | ||
* @return array simplified trace. | ||
*/ | ||
public function filter(array $trace): array | ||
{ | ||
$trace = array_slice($trace, 0, $this->maxTraceSourceLines); | ||
$trace = array_slice($trace, 0, $this->maxTraceSize); | ||
|
||
$result = []; | ||
foreach ($trace as $entry) { | ||
|
@@ -81,9 +91,9 @@ private function simplifyArgument($value) | |
} elseif (is_string($value)) { | ||
if (strlen($value) > 64) { | ||
return "'" . substr($value, 0, 64) . "...'"; | ||
} else { | ||
return "'" . $value . "'"; | ||
} | ||
|
||
return "'" . $value . "'"; | ||
} elseif (is_array($value)) { | ||
return '[' . $this->simplifyArguments($value) . ']'; | ||
} elseif ($value === null) { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
<?php | ||
|
||
namespace yii1tech\error\handler\test; | ||
|
||
use yii1tech\error\handler\ErrorTraceFilter; | ||
|
||
class ErrorTraceFilterTest extends TestCase | ||
{ | ||
protected function prepareException($exceptionMessage = 'Test exception', $exceptionCode = 12345) : \Throwable | ||
{ | ||
$args = [ | ||
'object' => new \stdClass(), | ||
'bool' => true, | ||
'string' => 'a string', | ||
// Managed argument count being at 5, create a sub bucket | ||
'sub' => [ | ||
'a_too_long_string' => str_repeat('a', 100), | ||
'null' => null, | ||
'resource' => tmpfile(), | ||
'sub' => [ | ||
'list' => ['foo', 'bar'], | ||
'list_with_holes' => [9 => 'nine', 5 => 'five'], | ||
'mixed_array' => ['foo', 'name' => 'bar'] | ||
] | ||
], | ||
'extra_param' => 'will not be normalized!' | ||
]; | ||
|
||
try { | ||
// create a long stack trace | ||
$closure = function ($exceptionMessage, $exceptionCode, array $allTypesOfArgs) { | ||
throw new \RuntimeException($exceptionMessage, $exceptionCode); | ||
}; | ||
|
||
call_user_func($closure, $exceptionMessage, $exceptionCode, $args); | ||
} catch (\Throwable $exception) { | ||
// shutdown test exception as prepared | ||
} | ||
|
||
return $exception; | ||
} | ||
|
||
public function testFilter(): void | ||
{ | ||
$traceFilter = new ErrorTraceFilter(); | ||
|
||
$exceptionMessage = 'Test exception'; | ||
$exceptionCode = 12345; | ||
|
||
$exception = $this->prepareException($exceptionMessage, $exceptionCode); | ||
|
||
$trace = $traceFilter->filter($exception->getTrace()); | ||
|
||
$this->assertFalse(empty($trace[0])); | ||
} | ||
|
||
/** | ||
* @depends testFilter | ||
*/ | ||
public function testShouldRestrictTraceSize(): void | ||
{ | ||
$exception = $this->prepareException(); | ||
|
||
$traceFilter = new ErrorTraceFilter(); | ||
$traceFilter->maxTraceSize = 1; | ||
|
||
$trace = $traceFilter->filter($exception->getTrace()); | ||
|
||
$this->assertCount($traceFilter->maxTraceSize, $trace); | ||
} | ||
|
||
/** | ||
* @depends testShouldRestrictTraceSize | ||
*/ | ||
public function testShouldShowTraceArguments(): void | ||
{ | ||
ini_set('zend.exception_ignore_args', 0); // Be sure arguments will be available on the stack trace | ||
$exception = $this->prepareException(); | ||
|
||
$traceFilter = new ErrorTraceFilter(); | ||
$traceFilter->maxTraceSize = 1; | ||
|
||
$trace = $traceFilter->filter($exception->getTrace()); | ||
|
||
$argsFound = false; | ||
foreach ($trace as $entry) { | ||
if (isset($entry['args'])) { | ||
$argsFound = true; | ||
break; | ||
} | ||
} | ||
|
||
$this->assertTrue($argsFound); | ||
} | ||
} |