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