diff --git a/lib/Catcher.php b/lib/Catcher.php
index 454d43d..ebd85c0 100644
--- a/lib/Catcher.php
+++ b/lib/Catcher.php
@@ -9,7 +9,7 @@ declare(strict_types=1);
namespace Mensbeam\Framework;
use Mensbeam\Framework\Catcher\{
ThrowableController,
- ThrowableHandler
+ Handler
};
@@ -17,32 +17,29 @@ class Catcher {
/**
* Array of handlers the exceptions are passed to
*
- * @var ThrowableHandler[]
+ * @var Handler[]
*/
protected array $handlers = [];
+ protected array $handlerClasses = [];
/** Flag set when the shutdown handler is run */
protected bool $isShuttingDown = false;
+ protected Map $results;
- public function __construct(ThrowableHandler ...$handlers) {
- $this->handlers = $handlers;
- $prev = set_error_handler([ $this, 'handleError' ]);
- $prev = set_exception_handler([ $this, 'handleThrowable' ]);
+
+ public function __construct(string ...$handlerClasses) {
+ $this->handlerClasses = $handlerClasses;
+ $this->results = new Map();
+
+ set_error_handler([ $this, 'handleError' ]);
+ 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
@@ -72,16 +69,26 @@ class Catcher {
*/
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)) {
+ foreach ($this->handlerClasses as $h) {
+ $handler = $h::create($controller);
+ $code = $handler->getOutputCode();
+
+ if ($code & Handler::OUTPUT_NOW) {
+ $handler->output();
+ } else {
+ $this->handlers[] = $handler;
+ }
+
+ if ($code & Handler::CONTINUE) {
break;
}
}
+ /*if ($this->isShuttingDown || $throwable instanceof \Exception || in_array($throwable->getCode(), [ \E_ERROR, \E_PARSE, \E_CORE_ERROR, \E_COMPILE_ERROR, \E_USER_ERROR ]) {
+
+ }*/
+
if (
- self::isHTTPRequest() ||
$this->isShuttingDown ||
$throwable instanceof \Exception ||
in_array($throwable->getCode(), [ \E_ERROR, \E_PARSE, \E_CORE_ERROR, \E_COMPILE_ERROR, \E_USER_ERROR ])
@@ -97,9 +104,7 @@ class Catcher {
*/
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 ])) {
+ if (error_get_last() && 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']);
}
}
diff --git a/lib/Catcher/HTMLHandler.php b/lib/Catcher/HTMLHandler.php
index 8c1042c..32a9346 100644
--- a/lib/Catcher/HTMLHandler.php
+++ b/lib/Catcher/HTMLHandler.php
@@ -9,64 +9,37 @@ declare(strict_types=1);
namespace Mensbeam\Framework\Catcher;
-class HTMLHandler extends ThrowableHandler {
+class HTMLHandler extends Handler {
public const CONTENT_TYPE = 'text/html';
/** The number of backtrace frames in which to print arguments; defaults to 5 */
- protected int $_backtraceArgFrameLimit = 5;
+ protected static int $_backtraceArgFrameLimit = 5;
/** If true the handler will output backtraces; defaults to false */
- protected bool $_outputBacktrace = false;
+ protected static bool $_outputBacktrace = false;
/** If true the handler will output previous throwables; defaults to true */
- protected bool $_outputPrevious = true;
+ protected static bool $_outputPrevious = true;
/** If true the handler will output times to the output; defaults to true */
- protected bool $_outputTime = true;
+ protected static bool $_outputTime = true;
/** The PHP-standard date format which to use for times printed to output */
- protected string $_timeFormat = 'H:i:s';
+ protected static string $_timeFormat = 'H:i:s';
- public function __construct(array $config = []) {
- parent::__construct($config);
- }
-
-
-
-
- public function getBacktraceArgFrameLimit(): int {
- return $this->_getBacktraceArgFrameLimit;
- }
-
- 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 {
+ public static function handle(\Throwable $throwable, ThrowableController $controller): bool {
$document = new \DOMDocument();
$frag = $document->createDocumentFragment();
- if ($this->_outputTime && $this->_timeFormat !== '') {
+ if (self::$_outputTime && self::$_timeFormat !== '') {
$p = $document->createElement('p');
$time = $document->createElement('time');
- $time->appendChild($document->createTextNode((new \DateTime())->format($this->_timeFormat)));
+ $time->appendChild($document->createTextNode((new \DateTime())->format(self::$_timeFormat)));
$p->appendChild($time);
$frag->appendChild($p);
}
- $frag->appendChild($this->buildThrowable($document, $throwable, $controller));
- if ($this->_outputPrevious) {
+ $frag->appendChild(self::$buildThrowable($document, $throwable, $controller));
+ if (self::$_outputPrevious) {
$prev = $throwable->getPrevious();
$prevController = $controller->getPrevious();
while ($prev) {
@@ -75,13 +48,13 @@ class HTMLHandler extends ThrowableHandler {
$small->appendChild($document->createTextNode('Caused by ↴'));
$p->appendChild($small);
$frag->appendChild($p);
- $frag->appendChild($this->buildThrowable($document, $prev, $prevController));
+ $frag->appendChild(self::$buildThrowable($document, $prev, $prevController));
$prev = $prev->getPrevious();
$prevController = $prevController->getPrevious();
}
}
- if ($this->_outputBacktrace) {
+ if (self::$_outputBacktrace) {
$frames = $controller->getFrames();
$p = $document->createElement('p');
$p->appendChild($document->createTextNode('Stack trace:'));
@@ -93,7 +66,7 @@ class HTMLHandler extends ThrowableHandler {
$num = 1;
foreach ($frames as $frame) {
$li = $document->createElement('li');
- $args = (!empty($frame['args']) && $this->_backtraceArgFrameLimit >= $num);
+ $args = (!empty($frame['args']) && self::$_backtraceArgFrameLimit >= $num);
$t = ($args) ? $document->createElement('p') : $li;
if (!empty($frame['error'])) {
@@ -141,9 +114,9 @@ class HTMLHandler extends ThrowableHandler {
}
}
- $this->_result = $frag;
+ $this->result = $frag;
- if (\PHP_SAPI !== 'cli' && $this->_output) {
+ if (\PHP_SAPI !== 'cli' && self::$_output) {
$document->loadHTML(sprintf(
'
%s%s',
(isset($_SERVER['protocol'])) ? "{$_SERVER['protocol']} " : '',
@@ -151,36 +124,17 @@ class HTMLHandler extends ThrowableHandler {
));
$document->getElementsByTagName('body')[0]->appendChild($document->importNode($frag, true));
- $this->sendContentTypeHeader();
+ self::$sendContentTypeHeader();
http_response_code(500);
echo $document->saveHTML();
- return (!$this->_passthrough);
+ return (!self::$_passthrough);
}
return false;
}
- public function setBacktraceArgFrameLimit(int $value): void {
- $this->_getBacktraceArgFrameLimit = $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 buildThrowable(\DOMDocument $document, \Throwable $throwable, ThrowableController $controller): \DOMElement {
+ protected static function buildThrowable(\DOMDocument $document, \Throwable $throwable, ThrowableController $controller): \DOMElement {
$p = $document->createElement('p');
$class = $throwable::class;
diff --git a/lib/Catcher/Handler.php b/lib/Catcher/Handler.php
new file mode 100644
index 0000000..6e054d3
--- /dev/null
+++ b/lib/Catcher/Handler.php
@@ -0,0 +1,102 @@
+controller = $controller;
+ $this->data = $data;
+
+ if (!self::$_silent) {
+ $this->outputCode = (!self::$_forceOutputNow) ? self::OUTPUT : self::OUTPUT_NOW;
+ } else {
+ $this->outputCode = self::SILENT;
+ }
+
+ if ($forceContinue) {
+ $this->outputCode |= self::CONTINUE;
+ return;
+ } elseif ($forceExit) {
+ $this->outputCode |= self::EXIT;
+ return;
+ }
+
+ if ($this->outputCode !== self::SILENT) {
+ $throwable = $controller->getThrowable();
+ if ($throwable instanceof \Exception || in_array($throwable->getCode(), [ \E_ERROR, \E_PARSE, \E_CORE_ERROR, \E_COMPILE_ERROR, \E_USER_ERROR ])) {
+ $this->outputCode |= self::EXIT;
+ return;
+ }
+ }
+
+ $this->outputCode |= ($this->outputCode === self::SILENT) ? self::CONTINUE : self::BREAK;
+ return;
+ }
+
+
+
+
+ public static function config(array $config = []) {
+ foreach ($config as $key => $value) {
+ $key = "_$key";
+ self::$$key = $value;
+ }
+
+ return __CLASS__;
+ }
+
+ abstract public static function create(ThrowableController $controller): self;
+
+
+ public function getOutputCode(): int {
+ return $this->outputCode;
+ }
+
+ public function getThrowable(): \Throwable {
+ return $this->throwable;
+ }
+
+ abstract public function output(): void;
+}
\ No newline at end of file
diff --git a/lib/Catcher/PlainTextHandler.php b/lib/Catcher/PlainTextHandler.php
index 0eaa487..6ca37e0 100644
--- a/lib/Catcher/PlainTextHandler.php
+++ b/lib/Catcher/PlainTextHandler.php
@@ -13,78 +13,123 @@ use \Psr\Log\{
};
-class PlainTextHandler extends ThrowableHandler implements LoggerAwareInterface {
+class PlainTextHandler extends Handler implements LoggerAwareInterface {
public const CONTENT_TYPE = 'text/plain';
/** The number of backtrace frames in which to print arguments; defaults to 5 */
- protected int $_backtraceArgFrameLimit = 5;
+ protected static int $_backtraceArgFrameLimit = 5;
/** The PSR-3 compatible logger in which to log to; defaults to null (no logging) */
- protected ?LoggerInterface $_logger = null;
+ protected static ?LoggerInterface $_logger = null;
/** If true the handler will output backtraces; defaults to false */
- protected bool $_outputBacktrace = false;
+ protected static bool $_outputBacktrace = false;
/** If true the handler will output previous throwables; defaults to true */
- protected bool $_outputPrevious = true;
+ protected static 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;
+ protected static bool $_outputTime = true;
/** The PHP-standard date format which to use for times printed to output */
- protected string $_timeFormat = '[H:i:s]';
-
+ protected static string $_timeFormat = '[H:i:s]';
+ public static function create(ThrowableController $controller): self {
+ $message = null;
+ if (self::$_logger !== null) {
+ $message = self::serialize($controller);
+ self::$log($controller->getThrowable(), $message);
+ }
- public function __construct(array $config = []) {
- parent::__construct($config);
+ return new self(
+ controller: $controller,
+ data: [ 'message' => $message ]
+ );
}
+ public function output(): void {
+ $message = $this->data['message'] ?? self::serialize($this->controller);
+ if (self::$_outputTime && self::$_timeFormat !== '') {
+ $time = (new \DateTime())->format(self::$_timeFormat) . ' ';
+ $timeStrlen = strlen($time);
+ $message = preg_replace('/^/m', str_repeat(' ', $timeStrlen), $message);
+ $message = preg_replace('/^ {' . $timeStrlen . '}/', $time, $message);
+ }
- public function getBacktraceArgFrameLimit(): int {
- return $this->_getBacktraceArgFrameLimit;
+ if (!Catcher::isHTTPRequest()) {
+ fprintf(\STDERR, "$message\n");
+ } else {
+ echo "$message\n";
+ }
}
- public function getLogger(): ?LoggerInterface {
- return $this->_logger;
- }
+ public function getOutputCode(): int {
+ if ($this->outputCode !== null) {
+ return $this->outputCode;
+ }
- public function getOutputBacktrace(): bool {
- return $this->_outputBacktrace;
+ $code = parent::getOutputCode();
+ // When the sapi is CLI we want to output as soon as possible if
+ $this->outputCode = ($code & self::OUTPUT === 0 && \PHP_SAPI === 'CLI') ? $code &~ self::OUTPUT | self::OUTPUT_NOW : $code;
+ return $this->outputCode;
}
- public function getOutputPrevious(): bool {
- return $this->_outputPrevious;
- }
- public function getOutputTime(): bool {
- return $this->_outputTime;
+ protected static function log(\Throwable $throwable, string $message): void {
+ if ($throwable instanceof \Error) {
+ switch ($throwable->getCode()) {
+ case \E_NOTICE:
+ case \E_USER_NOTICE:
+ case \E_STRICT:
+ self::$_logger->notice($message);
+ break;
+ case \E_WARNING:
+ case \E_COMPILE_WARNING:
+ case \E_USER_WARNING:
+ case \E_DEPRECATED:
+ case \E_USER_DEPRECATED:
+ self::$_logger->warning($message);
+ break;
+ case \E_RECOVERABLE_ERROR:
+ self::$_logger->error($message);
+ break;
+ case \E_PARSE:
+ case \E_CORE_ERROR:
+ case \E_COMPILE_ERROR:
+ self::$_logger->alert($message);
+ break;
+ }
+ } elseif ($throwable instanceof \Exception) {
+ if ($throwable instanceof \PharException || $throwable instanceof \RuntimeException) {
+ self::$_logger->alert($message);
+ }
+ } else {
+ self::$_logger->critical($message);
+ }
}
- public function getTimeFormat(): bool {
- return $this->_timeFormat;
- }
+ protected function prependTimestamps(string $message): string {
+ $time = (new \DateTime())->format(self::$_timeFormat) . ' ';
+ $timeStrlen = strlen($time);
- 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 = preg_replace('/^/m', str_repeat(' ', $timeStrlen), $message);
+ return preg_replace('/^ {' . $timeStrlen . '}/', $time, $message);
+ }
- $message = $this->serializeThrowable($throwable, $controller);
- if ($this->_outputPrevious) {
+ protected static function serialize(ThrowableController $controller): string {
+ $message = self::$serializeThrowable($controller);
+ if (self::$_outputPrevious) {
$prev = $throwable->getPrevious();
$prevController = $controller->getPrevious();
while ($prev) {
- $message .= sprintf("\n\nCaused by ↴\n%s", $this->serializeThrowable($prev, $prevController));
+ $message .= sprintf("\n\nCaused by ↴\n%s", self::$serializeThrowable($prev, $prevController));
$prev = $prev->getPrevious();
$prevController = $prevController->getPrevious();
}
}
- if ($this->_outputBacktrace) {
+ if (self::$_outputBacktrace) {
$frames = $controller->getFrames();
$message .= "\nStack trace:";
@@ -94,7 +139,7 @@ class PlainTextHandler extends ThrowableHandler implements LoggerAwareInterface
$function = $frame['function'] ?? '';
$args = '';
- if (!empty($frame['args']) && $this->_backtraceArgFrameLimit >= $num) {
+ if (!empty($frame['args']) && self::$_backtraceArgFrameLimit >= $num) {
$args = "\n" . preg_replace('/^/m', str_repeat(' ', strlen((string)$num) + 2) . '| ', var_export($frame['args'], true));
}
@@ -117,93 +162,11 @@ class PlainTextHandler extends ThrowableHandler implements LoggerAwareInterface
}
}
- 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);
+ return $message;
}
- protected function serializeThrowable(\Throwable $throwable, ThrowableController $controller): string {
+ protected static function serializeThrowable(ThrowableController $controller): string {
+ $throwable = $controller->getThrowable();
$class = $throwable::class;
if ($throwable instanceof \Error) {
$type = $controller->getErrorType();
diff --git a/lib/Catcher/ThrowableHandler.php b/lib/Catcher/ThrowableHandler.php
deleted file mode 100644
index 1b39429..0000000
--- a/lib/Catcher/ThrowableHandler.php
+++ /dev/null
@@ -1,72 +0,0 @@
- $value) {
- $key = "_$key";
- $this->$key = $value;
- }
- }
-
-
-
-
- public function getOutput(): bool {
- return $this->_output;
- }
-
- public function getPassthrough(): bool {
- return $this->_passthrough;
- }
-
- public function getResult(): mixed {
- return $this->_result;
- }
-
- 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::CONTENT_TYPE);
- }
-}
\ No newline at end of file