Dustin Wilson
2 years ago
commit
e6e99a1d5c
9 changed files with 788 additions and 0 deletions
@ -0,0 +1,76 @@ |
|||||
|
# Catcher-specific |
||||
|
/test*.html |
||||
|
/test*.php |
||||
|
/test/* |
||||
|
|
||||
|
# General |
||||
|
*.DS_Store |
||||
|
.AppleDouble |
||||
|
.LSOverride |
||||
|
|
||||
|
# Icon must end with two \r |
||||
|
Icon |
||||
|
|
||||
|
|
||||
|
# Thumbnails |
||||
|
._* |
||||
|
|
||||
|
# Files that might appear in the root of a volume |
||||
|
.DocumentRevisions-V100 |
||||
|
.fseventsd |
||||
|
.Spotlight-V100 |
||||
|
.TemporaryItems |
||||
|
.Trashes |
||||
|
.VolumeIcon.icns |
||||
|
.com.apple.timemachine.donotpresent |
||||
|
|
||||
|
# Directories potentially created on remote AFP share |
||||
|
.AppleDB |
||||
|
.AppleDesktop |
||||
|
Network Trash Folder |
||||
|
Temporary Items |
||||
|
.apdisk |
||||
|
|
||||
|
# Windows thumbnail cache files |
||||
|
Thumbs.db |
||||
|
ehthumbs.db |
||||
|
ehthumbs_vista.db |
||||
|
|
||||
|
# Dump file |
||||
|
*.stackdump |
||||
|
|
||||
|
# Folder config file |
||||
|
Desktop.ini |
||||
|
|
||||
|
# Recycle Bin used on file shares |
||||
|
$RECYCLE.BIN/ |
||||
|
|
||||
|
# Windows Installer files |
||||
|
*.cab |
||||
|
*.msi |
||||
|
*.msm |
||||
|
*.msp |
||||
|
|
||||
|
# Windows shortcuts |
||||
|
*.lnk |
||||
|
|
||||
|
*~ |
||||
|
|
||||
|
# temporary files which can be created if a process still has a handle open of a deleted file |
||||
|
.fuse_hidden* |
||||
|
|
||||
|
# KDE directory preferences |
||||
|
.directory |
||||
|
|
||||
|
# Linux trash folder which might appear on any partition or disk |
||||
|
.Trash-* |
||||
|
|
||||
|
# .nfs files are created when an open file is removed but is still being accessed |
||||
|
.nfs* |
||||
|
|
||||
|
/vendor/ |
||||
|
/vendor-bin/*/vendor |
||||
|
/tests/html5lib-tests |
||||
|
/tests/.phpunit.result.cache |
||||
|
/tests/coverage |
||||
|
cachegrind.out.* |
@ -0,0 +1,12 @@ |
|||||
|
# Catcher |
||||
|
|
||||
|
Catcher is a Throwable catcher and error handling library for PHP. |
||||
|
|
||||
|
## Example |
||||
|
|
||||
|
```php |
||||
|
$catcher = new Catcher(new PlainTextHandler([ |
||||
|
'outputBacktrace' => true, |
||||
|
'backtraceArgFrameLimit' => 2 |
||||
|
])); |
||||
|
``` |
@ -0,0 +1,22 @@ |
|||||
|
{ |
||||
|
"name": "mensbeam/catcher", |
||||
|
"description": "Catches exceptions, errors, and shutdowns", |
||||
|
"type": "library", |
||||
|
"license": "MIT", |
||||
|
"autoload": { |
||||
|
"psr-4": { |
||||
|
"Mensbeam\\Framework\\": "lib/", |
||||
|
"Mensbeam\\Framework\\Test\\": "test/" |
||||
|
} |
||||
|
}, |
||||
|
"authors": [ |
||||
|
{ |
||||
|
"name": "Dustin Wilson", |
||||
|
"email": "dustin@dustinwilson.com" |
||||
|
} |
||||
|
], |
||||
|
"require": { |
||||
|
"php": ">=8.1", |
||||
|
"psr/log": "^3.0" |
||||
|
} |
||||
|
} |
@ -0,0 +1,71 @@ |
|||||
|
{ |
||||
|
"_readme": [ |
||||
|
"This file locks the dependencies of your project to a known state", |
||||
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", |
||||
|
"This file is @generated automatically" |
||||
|
], |
||||
|
"content-hash": "8998cce6e05b3ccc6aca7c736416f570", |
||||
|
"packages": [ |
||||
|
{ |
||||
|
"name": "psr/log", |
||||
|
"version": "3.0.0", |
||||
|
"source": { |
||||
|
"type": "git", |
||||
|
"url": "https://github.com/php-fig/log.git", |
||||
|
"reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" |
||||
|
}, |
||||
|
"dist": { |
||||
|
"type": "zip", |
||||
|
"url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", |
||||
|
"reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", |
||||
|
"shasum": "" |
||||
|
}, |
||||
|
"require": { |
||||
|
"php": ">=8.0.0" |
||||
|
}, |
||||
|
"type": "library", |
||||
|
"extra": { |
||||
|
"branch-alias": { |
||||
|
"dev-master": "3.x-dev" |
||||
|
} |
||||
|
}, |
||||
|
"autoload": { |
||||
|
"psr-4": { |
||||
|
"Psr\\Log\\": "src" |
||||
|
} |
||||
|
}, |
||||
|
"notification-url": "https://packagist.org/downloads/", |
||||
|
"license": [ |
||||
|
"MIT" |
||||
|
], |
||||
|
"authors": [ |
||||
|
{ |
||||
|
"name": "PHP-FIG", |
||||
|
"homepage": "https://www.php-fig.org/" |
||||
|
} |
||||
|
], |
||||
|
"description": "Common interface for logging libraries", |
||||
|
"homepage": "https://github.com/php-fig/log", |
||||
|
"keywords": [ |
||||
|
"log", |
||||
|
"psr", |
||||
|
"psr-3" |
||||
|
], |
||||
|
"support": { |
||||
|
"source": "https://github.com/php-fig/log/tree/3.0.0" |
||||
|
}, |
||||
|
"time": "2021-07-14T16:46:02+00:00" |
||||
|
} |
||||
|
], |
||||
|
"packages-dev": [], |
||||
|
"aliases": [], |
||||
|
"minimum-stability": "stable", |
||||
|
"stability-flags": [], |
||||
|
"prefer-stable": false, |
||||
|
"prefer-lowest": false, |
||||
|
"platform": { |
||||
|
"php": ">=8.1" |
||||
|
}, |
||||
|
"platform-dev": [], |
||||
|
"plugin-api-version": "2.3.0" |
||||
|
} |
@ -0,0 +1,101 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* @license MIT |
||||
|
* Copyright 2022 Dustin Wilson, et al. |
||||
|
* See LICENSE and AUTHORS files for details |
||||
|
*/ |
||||
|
|
||||
|
declare(strict_types=1); |
||||
|
namespace Mensbeam\Framework; |
||||
|
use Mensbeam\Framework\Catcher\{ |
||||
|
ThrowableController, |
||||
|
ThrowableHandler |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
class Catcher { |
||||
|
/** |
||||
|
* Array of handlers the exceptions are passed to |
||||
|
* |
||||
|
* @var ThrowableHandler[] |
||||
|
*/ |
||||
|
protected array $handlers = []; |
||||
|
/** Flag set when the shutdown handler is run */ |
||||
|
protected bool $isShuttingDown = false; |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
public function __construct(ThrowableHandler ...$handlers) { |
||||
|
$this->handlers = $handlers; |
||||
|
$prev = set_error_handler([ $this, 'handleError' ]); |
||||
|
$prev = set_exception_handler([ $this, 'handleThrowable' ]); |
||||
|
register_shutdown_function([ $this, 'handleShutdown' ]); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* Gets the list of handlers |
||||
|
* |
||||
|
* @return ThrowableHandler[] |
||||
|
*/ |
||||
|
public function getHandlers(): array { |
||||
|
return $this->handlers; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Converts regular errors into throwable Errors for easier handling; meant to be |
||||
|
* used with set_error_handler. |
||||
|
* |
||||
|
* @internal |
||||
|
*/ |
||||
|
public function handleError(int $code, string $message, ?string $file = null, ?int $line = null): bool { |
||||
|
if ($code !== 0 && error_reporting()) { |
||||
|
$error = new Error($message, $code, $file, $line); |
||||
|
if ($this->isShuttingDown) { |
||||
|
throw $error; |
||||
|
} else { |
||||
|
$this->handleThrowable($error); |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Handles both Exceptions and Errors; meant to be used with set_exception_handler. |
||||
|
* |
||||
|
* @internal |
||||
|
*/ |
||||
|
public function handleThrowable(\Throwable $throwable): void { |
||||
|
$controller = new ThrowableController($throwable); |
||||
|
foreach ($this->handlers as $h) { |
||||
|
// If the handler returns true it means the handler handled the exception properly, |
||||
|
// and there's no reason to pass it off to another. |
||||
|
if ($h->handle($throwable, $controller)) { |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if ($this->isShuttingDown || $throwable instanceof \Exception || in_array($throwable->getCode(), [ \E_ERROR, \E_PARSE, \E_CORE_ERROR, \E_COMPILE_ERROR, \E_USER_ERROR ])) { |
||||
|
exit($throwable->getCode()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Handles shutdowns, passes all possible built-in error codes to the error handler. |
||||
|
* |
||||
|
* @internal |
||||
|
*/ |
||||
|
public function handleShutdown() { |
||||
|
$this->isShuttingDown = true; |
||||
|
|
||||
|
$error = error_get_last(); |
||||
|
if ($error && in_array($error['type'], [ \E_ERROR, \E_PARSE, \E_CORE_ERROR, \E_CORE_WARNING, \E_COMPILE_ERROR, \E_COMPILE_WARNING ])) { |
||||
|
$this->handleError($error['type'], $error['message'], $error['file'], $error['line']); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,221 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* @license MIT |
||||
|
* Copyright 2022 Dustin Wilson, et al. |
||||
|
* See LICENSE and AUTHORS files for details |
||||
|
*/ |
||||
|
|
||||
|
declare(strict_types=1); |
||||
|
namespace Mensbeam\Framework\Catcher; |
||||
|
use \Psr\Log\{ |
||||
|
LoggerAwareInterface, |
||||
|
LoggerInterface |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
class PlainTextHandler extends ThrowableHandler implements LoggerAwareInterface { |
||||
|
protected static ?string $contentType = 'text/plain'; |
||||
|
|
||||
|
/** The number of backtrace frames in which to print arguments; defaults to 5 */ |
||||
|
protected int $_backtraceArgFrameLimit = 5; |
||||
|
/** The PSR-3 compatible logger in which to log to; defaults to null (no logging) */ |
||||
|
protected ?LoggerInterface $_logger = null; |
||||
|
/** If true the handler will output backtraces; defaults to false */ |
||||
|
protected bool $_outputBacktrace = false; |
||||
|
/** If true the handler will output previous throwables; defaults to true */ |
||||
|
protected bool $_outputPrevious = true; |
||||
|
/** |
||||
|
* If true the handler will output times to the output. This is ignored by the |
||||
|
* logger which should have its own timestamping methods; defaults to true |
||||
|
*/ |
||||
|
protected bool $_outputTime = true; |
||||
|
/** The PHP-standard date format which to use for times printed to output */ |
||||
|
protected string $_timeFormat = '[H:i:s]'; |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
public function __construct(array $config = []) { |
||||
|
parent::__construct($config); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
public function getBacktraceArgFrameLimit(): int { |
||||
|
return $this->_getBacktraceArgFrameLimit; |
||||
|
} |
||||
|
|
||||
|
public function getLogger(): ?LoggerInterface { |
||||
|
return $this->_logger; |
||||
|
} |
||||
|
|
||||
|
public function getOutputBacktrace(): bool { |
||||
|
return $this->_outputBacktrace; |
||||
|
} |
||||
|
|
||||
|
public function getOutputPrevious(): bool { |
||||
|
return $this->_outputPrevious; |
||||
|
} |
||||
|
|
||||
|
public function getOutputTime(): bool { |
||||
|
return $this->_outputTime; |
||||
|
} |
||||
|
|
||||
|
public function getTimeFormat(): bool { |
||||
|
return $this->_timeFormat; |
||||
|
} |
||||
|
|
||||
|
public function handle(\Throwable $throwable, ThrowableController $controller): bool { |
||||
|
// If this can't output and there's no logger to log to then there's nothing to do |
||||
|
// here. Continue on to the next handler. |
||||
|
if (!$this->_output && $this->_logger === null) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
$message = $this->serializeThrowable($throwable, $controller); |
||||
|
if ($this->_outputPrevious) { |
||||
|
$prev = $throwable->getPrevious(); |
||||
|
$prevController = $controller->getPrevious(); |
||||
|
while ($prev) { |
||||
|
$message .= sprintf("\n\nCaused by ↴\n%s", $this->serializeThrowable($prev, $prevController)); |
||||
|
$prev = $prev->getPrevious(); |
||||
|
$prevController = $prevController->getPrevious(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if ($this->_outputBacktrace) { |
||||
|
$frames = $controller->getFrames(); |
||||
|
$message .= "\nStack trace:"; |
||||
|
|
||||
|
$num = 1; |
||||
|
foreach ($frames as $frame) { |
||||
|
$class = (!empty($frame['error'])) ? "{$frame['error']} ({$frame['class']})" : $frame['class'] ?? ''; |
||||
|
$function = $frame['function'] ?? ''; |
||||
|
|
||||
|
$args = ''; |
||||
|
if (!empty($frame['args']) && $this->_backtraceArgFrameLimit >= $num) { |
||||
|
$args = "\n" . preg_replace('/^/m', str_repeat(' ', strlen((string)$num) + 2) . '| ', var_export($frame['args'], true)); |
||||
|
} |
||||
|
|
||||
|
$template = "\n%3d. %s"; |
||||
|
if ($class && $function) { |
||||
|
$template .= '::'; |
||||
|
} |
||||
|
$template .= ($function) ? '%s()' : '%s'; |
||||
|
$template .= ' %s:%d%s'; |
||||
|
|
||||
|
$message .= sprintf( |
||||
|
"$template\n", |
||||
|
$num++, |
||||
|
$class, |
||||
|
$function, |
||||
|
$frame['file'], |
||||
|
$frame['line'], |
||||
|
$args |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if ($this->_logger !== null) { |
||||
|
$this->log($throwable, $message); |
||||
|
} |
||||
|
|
||||
|
if ($this->_output) { |
||||
|
// Logger handles its own timestamps |
||||
|
if ($this->_outputTime && $this->_timeFormat !== '') { |
||||
|
$time = (new \DateTime())->format($this->_timeFormat) . ' '; |
||||
|
$timeStrlen = strlen($time); |
||||
|
|
||||
|
$message = preg_replace('/^/m', str_repeat(' ', $timeStrlen), $message); |
||||
|
$message = preg_replace('/^ {' . $timeStrlen . '}/', $time, $message); |
||||
|
} |
||||
|
|
||||
|
if (\PHP_SAPI === 'cli') { |
||||
|
fprintf(\STDERR, "$message\n"); |
||||
|
} else { |
||||
|
$this->sendContentTypeHeader(); |
||||
|
http_response_code(500); |
||||
|
echo "$message\n"; |
||||
|
} |
||||
|
|
||||
|
return (!$this->_passthrough); |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
public function setBacktraceArgFrameLimit(int $value): void { |
||||
|
$this->_getBacktraceArgFrameLimit = $value; |
||||
|
} |
||||
|
|
||||
|
public function setLogger(?LoggerInterface $value): void { |
||||
|
$this->_logger = $value; |
||||
|
} |
||||
|
|
||||
|
public function setOutputBacktrace(bool $value): void { |
||||
|
$this->_outputBacktrace = $value; |
||||
|
} |
||||
|
|
||||
|
public function setOutputPrevious(bool $value): void { |
||||
|
$this->_outputPrevious = $value; |
||||
|
} |
||||
|
|
||||
|
public function setOutputTime(bool $value): void { |
||||
|
$this->_outputTime = $value; |
||||
|
} |
||||
|
|
||||
|
public function setTimeFormat(bool $value): void { |
||||
|
$this->_timeFormat = $value; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
protected function log(\Throwable $throwable, string $message): void { |
||||
|
if ($throwable instanceof \Error) { |
||||
|
switch ($throwable->getCode()) { |
||||
|
case \E_NOTICE: |
||||
|
case \E_USER_NOTICE: |
||||
|
case \E_STRICT: |
||||
|
$this->_logger->notice($message); |
||||
|
break; |
||||
|
case \E_WARNING: |
||||
|
case \E_COMPILE_WARNING: |
||||
|
case \E_USER_WARNING: |
||||
|
case \E_DEPRECATED: |
||||
|
case \E_USER_DEPRECATED: |
||||
|
$this->_logger->warning($message); |
||||
|
break; |
||||
|
case \E_RECOVERABLE_ERROR: |
||||
|
$this->_logger->error($message); |
||||
|
break; |
||||
|
case \E_PARSE: |
||||
|
case \E_CORE_ERROR: |
||||
|
case \E_COMPILE_ERROR: |
||||
|
$this->_logger->alert($message); |
||||
|
break; |
||||
|
} |
||||
|
} elseif ($throwable instanceof \Exception) { |
||||
|
if ($throwable instanceof \PharException || $throwable instanceof \RuntimeException) { |
||||
|
$this->_logger->alert($message); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
$this->_logger->critical($message); |
||||
|
} |
||||
|
|
||||
|
protected function serializeThrowable(\Throwable $throwable, ThrowableController $controller): string { |
||||
|
$class = $throwable::class; |
||||
|
if ($throwable instanceof \Error) { |
||||
|
$type = $controller->getErrorType(); |
||||
|
$class = ($type !== null) ? "$type (" . $throwable::class . ")" : $throwable::class; |
||||
|
} |
||||
|
|
||||
|
return sprintf( |
||||
|
'%s: %s in file %s on line %d', |
||||
|
$class, |
||||
|
$throwable->getMessage(), |
||||
|
$throwable->getFile(), |
||||
|
$throwable->getLine() |
||||
|
); |
||||
|
} |
||||
|
} |
@ -0,0 +1,202 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* @license MIT |
||||
|
* Copyright 2022 Dustin Wilson, et al. |
||||
|
* See LICENSE and AUTHORS files for details |
||||
|
*/ |
||||
|
|
||||
|
declare(strict_types=1); |
||||
|
namespace Mensbeam\Framework\Catcher; |
||||
|
|
||||
|
|
||||
|
class ThrowableController { |
||||
|
private string|bool|null $errorType = false; |
||||
|
private ?array $frames = null; |
||||
|
private ThrowableController|bool|null $previousThrowableController = false; |
||||
|
|
||||
|
private \Throwable $throwable; |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
public function __construct(\Throwable $throwable) { |
||||
|
$this->throwable = $throwable; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
/** Gets the type name for an Error object */ |
||||
|
public function getErrorType(): ?string { |
||||
|
if ($this->errorType !== false) { |
||||
|
return $this->errorType; |
||||
|
} |
||||
|
|
||||
|
if (!$this->throwable instanceof \Error) { |
||||
|
$this->errorType = null; |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
switch ($this->throwable->getCode()) { |
||||
|
case \E_ERROR: |
||||
|
$this->errorType = 'PHP Fatal Error'; |
||||
|
break; |
||||
|
case \E_WARNING: |
||||
|
$this->errorType = 'PHP Warning'; |
||||
|
break; |
||||
|
case \E_PARSE: |
||||
|
$this->errorType = 'PHP Parsing Error'; |
||||
|
break; |
||||
|
case \E_NOTICE: |
||||
|
$this->errorType = 'PHP Notice'; |
||||
|
break; |
||||
|
case \E_CORE_ERROR: |
||||
|
$this->errorType = 'PHP Core Error'; |
||||
|
break; |
||||
|
case \E_CORE_WARNING: |
||||
|
$this->errorType = 'PHP Core Warning'; |
||||
|
break; |
||||
|
case \E_COMPILE_ERROR: |
||||
|
$this->errorType = 'Compile Error'; |
||||
|
break; |
||||
|
case \E_COMPILE_WARNING: |
||||
|
$this->errorType = 'Compile Warning'; |
||||
|
break; |
||||
|
case \E_STRICT: |
||||
|
$this->errorType = 'Runtime Notice'; |
||||
|
break; |
||||
|
case \E_RECOVERABLE_ERROR: |
||||
|
$this->errorType = 'Recoverable Error'; |
||||
|
break; |
||||
|
case \E_DEPRECATED: |
||||
|
case \E_USER_DEPRECATED: |
||||
|
$this->errorType = 'Deprecated'; |
||||
|
break; |
||||
|
case \E_USER_ERROR: |
||||
|
$this->errorType = 'Fatal Error'; |
||||
|
break; |
||||
|
case \E_USER_WARNING: |
||||
|
$this->errorType = 'Warning'; |
||||
|
break; |
||||
|
case \E_USER_NOTICE: |
||||
|
$this->errorType = 'Notice'; |
||||
|
break; |
||||
|
default: |
||||
|
$this->errorType = null; |
||||
|
} |
||||
|
|
||||
|
return $this->errorType; |
||||
|
} |
||||
|
|
||||
|
/** Gets backtrace frames */ |
||||
|
public function getFrames(): array { |
||||
|
if ($this->frames !== null) { |
||||
|
return $this->frames; |
||||
|
} |
||||
|
|
||||
|
if ( |
||||
|
!$this->throwable instanceof \Error || |
||||
|
!in_array($this->throwable->getCode(), [ E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING ]) || |
||||
|
!extension_loaded('xdebug') || |
||||
|
!function_exists('xdebug_info') || |
||||
|
sizeof(xdebug_info('mode')) === 0 |
||||
|
) { |
||||
|
$frames = $this->throwable->getTrace(); |
||||
|
} else { |
||||
|
$frames = array_diff_key(array_reverse(xdebug_get_function_stack()), debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS)); |
||||
|
} |
||||
|
|
||||
|
// PHP for some stupid reason thinks it's okay not to provide line numbers and file |
||||
|
// names when using call_user_func_array; this fixes that. |
||||
|
// (https://bugs.php.net/bug.php?id=44428) |
||||
|
foreach ($frames as $key => $frame) { |
||||
|
if (empty($frame['file'])) { |
||||
|
$file = '[INTERNAL]'; |
||||
|
$line = 0; |
||||
|
|
||||
|
$next = $frames[$key + 1] ?? []; |
||||
|
|
||||
|
if ( |
||||
|
!empty($frame['file']) && |
||||
|
!empty($frame['function']) && |
||||
|
!empty($frame['line']) && |
||||
|
str_contains($frame['function'], 'call_user_func') |
||||
|
) { |
||||
|
$file = $next['file']; |
||||
|
$line = $next['line']; |
||||
|
} |
||||
|
|
||||
|
$frames[$key]['file'] = $file; |
||||
|
$frames[$key]['line'] = $line; |
||||
|
} |
||||
|
|
||||
|
$frames[$key]['line'] = (int)$frames[$key]['line']; |
||||
|
} |
||||
|
|
||||
|
// Delete everything that has anything to do with userland error handling |
||||
|
for ($frameCount = count($frames), $i = $frameCount - 1; $i >= 0; $i--) { |
||||
|
$frame = $frames[$i]; |
||||
|
if ($frame['file'] === $this->throwable->getFile() && $frame['line'] === $this->throwable->getLine()) { |
||||
|
array_splice($frames, 0, $i); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Add a frame for the throwable to the beginning of the array |
||||
|
$f = [ |
||||
|
'file' => $this->throwable->getFile(), |
||||
|
'line' => (int)$this->throwable->getLine(), |
||||
|
'class' => $this->throwable::class, |
||||
|
'args' => [ |
||||
|
$this->throwable->getMessage() |
||||
|
] |
||||
|
]; |
||||
|
|
||||
|
// Add the error name if it is an Error. |
||||
|
if ($this->throwable instanceof \Error) { |
||||
|
$error = $this->getErrorType(); |
||||
|
if ($error !== null) { |
||||
|
$f['error'] = $error; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
array_unshift($frames, $f); |
||||
|
|
||||
|
// Go through previous throwables and merge in their frames |
||||
|
if ($prev = $this->getPrevious()) { |
||||
|
$a = $frames; |
||||
|
$b = $prev->getFrames(); |
||||
|
$prevThrowable = $prev->getThrowable(); |
||||
|
|
||||
|
$diff = $a; |
||||
|
for ($i = count($a) - 1, $j = count($b) - 1; $i >= 0 && $j >= 0; $i--, $j--) { |
||||
|
$af = $diff[$i]['file']; |
||||
|
$bf = $b[$j]['file']; |
||||
|
if ($af && $bf && $af === $bf && $diff[$i]['line'] === $b[$j]['line']) { |
||||
|
unset($diff[$i]); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
$frames = [ ...$diff, ...$b ]; |
||||
|
} |
||||
|
|
||||
|
$this->frames = $frames; |
||||
|
return $frames; |
||||
|
} |
||||
|
|
||||
|
public function getPrevious(): ?ThrowableController { |
||||
|
if ($this->previousThrowableController !== false) { |
||||
|
return $this->previousThrowableController; |
||||
|
} |
||||
|
|
||||
|
if ($prev = $this->throwable->getPrevious()) { |
||||
|
$prev = new ThrowableController($prev); |
||||
|
} |
||||
|
|
||||
|
$this->previousThrowableController = $prev; |
||||
|
return $prev; |
||||
|
} |
||||
|
|
||||
|
public function getThrowable(): \Throwable { |
||||
|
return $this->throwable; |
||||
|
} |
||||
|
} |
@ -0,0 +1,66 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* @license MIT |
||||
|
* Copyright 2022 Dustin Wilson, et al. |
||||
|
* See LICENSE and AUTHORS files for details |
||||
|
*/ |
||||
|
|
||||
|
declare(strict_types=1); |
||||
|
namespace Mensbeam\Framework\Catcher; |
||||
|
|
||||
|
|
||||
|
abstract class ThrowableHandler { |
||||
|
protected static ?string $contentType = null; |
||||
|
|
||||
|
/** If true the handler will output data; if false it will be silent */ |
||||
|
protected bool $_output = true; |
||||
|
/** |
||||
|
* If true the handler will pass on through to the next handler even if it |
||||
|
* successfully handles the throwable; if false it will prevent execution of the |
||||
|
* next handler if it successfully handles the throwable |
||||
|
*/ |
||||
|
protected bool $_passthrough = false; |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
public function __construct(array $config = []) { |
||||
|
foreach ($config as $key => $value) { |
||||
|
$key = "_$key"; |
||||
|
$this->$key = $value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
public function getContentType(): ?string { |
||||
|
return static::$contentType; |
||||
|
} |
||||
|
|
||||
|
public function getOutput(): bool { |
||||
|
return $this->_output; |
||||
|
} |
||||
|
|
||||
|
public function getPassthrough(): bool { |
||||
|
return $this->_passthrough; |
||||
|
} |
||||
|
|
||||
|
abstract public function handle(\Throwable $throwable, ThrowableController $controller): bool; |
||||
|
|
||||
|
public function setOutput(bool $value): void { |
||||
|
$this->_output = $value; |
||||
|
} |
||||
|
|
||||
|
public function setPassthrough(bool $value): void { |
||||
|
$this->_passthrough = $value; |
||||
|
} |
||||
|
|
||||
|
protected function sendContentTypeHeader(): void { |
||||
|
if (!isset($_SERVER['REQUEST_URI']) || headers_sent()) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
header('Content-Type: ' . static::$contentType); |
||||
|
} |
||||
|
} |
@ -0,0 +1,17 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* @license MIT |
||||
|
* Copyright 2022 Dustin Wilson, et al. |
||||
|
* See LICENSE and AUTHORS files for details |
||||
|
*/ |
||||
|
|
||||
|
declare(strict_types=1); |
||||
|
namespace Mensbeam\Framework; |
||||
|
|
||||
|
class Error extends \Error { |
||||
|
public function __construct(string $message = '', int $code = 0, ?string $file = null, ?int $line = line, ?\Throwable $previous = null) { |
||||
|
parent::__construct($message, $code, $previous); |
||||
|
$this->file = $file; |
||||
|
$this->line = $line; |
||||
|
} |
||||
|
} |
Loading…
Reference in new issue