diff --git a/lib/Catcher.php b/lib/Catcher.php
index 11c1b8f..ebecaad 100644
--- a/lib/Catcher.php
+++ b/lib/Catcher.php
@@ -179,11 +179,11 @@ class Catcher {
$controller = new ThrowableController($throwable);
foreach ($this->handlers as $h) {
$output = $h->handle($controller);
- if ($output->outputCode & Handler::NOW) {
+ if ($output['outputCode'] & Handler::NOW) {
$h->dispatch();
}
- $controlCode = $output->controlCode;
+ $controlCode = $output['controlCode'];
if ($controlCode & Handler::BREAK) {
break;
}
diff --git a/lib/Catcher/HTMLHandler.php b/lib/Catcher/HTMLHandler.php
index 70aa60e..aac314d 100644
--- a/lib/Catcher/HTMLHandler.php
+++ b/lib/Catcher/HTMLHandler.php
@@ -17,8 +17,6 @@ class HTMLHandler extends Handler {
protected ?\DOMDocument $_document = null;
/** The XPath path to the element where the errors should be inserted */
protected string $_errorPath = '/html/body';
- /** If true the handler will output times to the output; 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';
@@ -44,8 +42,8 @@ class HTMLHandler extends Handler {
$this->xpath = new \DOMXPath($this->_document);
$location = $this->xpath->query($this->_errorPath);
- if (count($location) === 0 || !$location->item(0) instanceof \DOMElement) {
- throw new \InvalidArgumentException('Option "errorPath" must correspond to a location that is an instance of \DOMElement');
+ if (count($location) === 0 || (!$location->item(0) instanceof \DOMElement && !$location->item(0) instanceof \DOMDocumentFragment)) {
+ throw new \InvalidArgumentException('Option "errorPath" must correspond to a location that is an instance of \DOMElement or \DOMDocumentFragment');
}
$this->errorLocation = $location->item(0);
}
@@ -53,161 +51,145 @@ class HTMLHandler extends Handler {
- protected function buildThrowable(ThrowableController $controller): \DOMDocumentFragment {
- $throwable = $controller->getThrowable();
+ protected function buildOutputThrowable(array $outputThrowable, bool $previous = false): \DOMDocumentFragment {
$frag = $this->_document->createDocumentFragment();
+ $tFrag = $this->_document->createDocumentFragment();
+ $ip = $frag;
+ $hasSiblings = false;
- $b = $this->_document->createElement('b');
- $type = $controller->getErrorType();
- $class = $throwable::class;
- $b->appendChild($this->_document->createTextNode($type ?? $class));
- if ($type !== null) {
- $b->firstChild->textContent .= ' ';
- $code = $this->_document->createElement('code');
- $code->appendChild($this->_document->createTextNode("($class)"));
- $b->appendChild($code);
+ if ($previous === false) {
+ if (isset($outputThrowable['time'])) {
+ $p = $this->_document->createElement('p');
+ $time = $this->_document->createElement('time');
+ $time->setAttribute('datetime', $outputThrowable['time']->setTimezone(new \DateTimeZone('UTC'))->format('Y-m-d\TH:i:s.vO'));
+ $time->appendChild($this->_document->createTextNode($outputThrowable['time']->format($this->_timeFormat)));
+ $p->appendChild($time);
+ $frag->appendChild($p);
+
+ $div = $this->_document->createElement('div');
+ $frag->appendChild($div);
+ $ip = $div;
+ }
+ } else {
+ $span = $this->_document->createElement('span');
+ $span->appendChild($this->_document->createTextNode('Caused by:'));
+ $tFrag->appendChild($span);
+ $tFrag->appendChild($this->_document->createTextNode(' '));
}
- $frag->appendChild($b);
- $frag->appendChild($this->_document->createTextNode(': '));
+ $b = $this->_document->createElement('b');
+ $code = $this->_document->createElement('code');
+ $code->appendChild($this->_document->createTextNode($outputThrowable['class']));
+ $b->appendChild($code);
+ if (isset($outputThrowable['errorType'])) {
+ $b->insertBefore($this->_document->createTextNode("{$outputThrowable['errorType']} ("), $code);
+ $b->appendChild($this->_document->createTextNode(')'));
+ }
+ $tFrag->appendChild($b);
+ $tFrag->appendChild($this->_document->createTextNode(': '));
$i = $this->_document->createElement('i');
- $i->appendChild($this->_document->createTextNode($throwable->getMessage()));
- $frag->appendChild($i);
- $frag->appendChild($this->_document->createTextNode(' in file '));
+ $i->appendChild($this->_document->createTextNode($outputThrowable['message']));
+ $tFrag->appendChild($i);
+ $tFrag->appendChild($this->_document->createTextNode(' in file '));
$code = $this->_document->createElement('code');
- $code->appendChild($this->_document->createTextNode($throwable->getFile()));
- $frag->appendChild($code);
- $frag->appendChild($this->_document->createTextNode(' on line ' . $throwable->getLine()));
- return $frag;
- }
-
- protected function dispatchCallback(): void {
- $ul = $this->_document->createElement('ul');
- $this->errorLocation->appendChild($ul);
-
- $allSilent = true;
- foreach ($this->outputBuffer as $o) {
- if ($o->outputCode & self::SILENT) {
- continue;
- }
+ $code->appendChild($this->_document->createTextNode($outputThrowable['file']));
+ $tFrag->appendChild($code);
+ $tFrag->appendChild($this->_document->createTextNode(" on line {$outputThrowable['line']}"));
- $allSilent = false;
+ if (isset($outputThrowable['previous'])) {
+ $ul = $this->_document->createElement('ul');
$li = $this->_document->createElement('li');
- $li->appendChild($o->output);
+ $li->appendChild($this->buildOutputThrowable($outputThrowable['previous'], true));
$ul->appendChild($li);
+ $ip->appendChild($ul);
+ $hasSiblings = true;
}
- if (!$allSilent) {
- $this->print($this->_document->saveHTML());
- }
- }
+ if ($previous === false && isset($outputThrowable['frames'])) {
+ $p = $this->_document->createElement('p');
+ $p->appendChild($this->_document->createTextNode('Stack trace:'));
+ $ip->appendChild($p);
+
+ $ol = $this->_document->createElement('ol');
+ $ip->appendChild($ol);
+ foreach ($outputThrowable['frames'] as $frame) {
+ $li = $this->_document->createElement('li');
+ $ol->appendChild($li);
+ if (isset($frame['args'])) {
+ $t = $this->_document->createElement('p');
+ $li->appendChild($t);
+ } else {
+ $t = $li;
+ }
+ $b = $this->_document->createElement('b');
+ $code = $this->_document->createElement('code');
+ $b->appendChild($code);
+ $t->appendChild($b);
+
+ if (isset($frame['class'])) {
+ $code->appendChild($this->_document->createTextNode($frame['class']));
+
+ if (isset($frame['errorType'])) {
+ $b->insertBefore($this->_document->createTextNode("{$frame['errorType']} ("), $code);
+ $b->appendChild($this->_document->createTextNode(')'));
+ } elseif (isset($frame['function'])) {
+ $code->firstChild->appendData("::{$frame['function']}");
+ }
+ } elseif (!empty($frame['function'])) {
+ $code->appendChild($this->_document->createTextNode($frame['function']));
+ }
- protected function handleCallback(ThrowableController $controller): HandlerOutput {
- $frag = $this->_document->createDocumentFragment();
+ $t->appendChild($this->_document->createTextNode("\u{00a0}\u{00a0}"));
+ $code = $this->_document->createElement('code');
+ $code->appendChild($this->_document->createTextNode($frame['file']));
+ $t->appendChild($code);
+ $t->appendChild($this->_document->createTextNode(":{$frame['line']}"));
- if ($this->_outputTime && $this->_timeFormat !== '') {
- $p = $this->_document->createElement('p');
- $time = $this->_document->createElement('time');
- $now = new \DateTimeImmutable();
- $tz = $now->getTimezone()->getName();
- if ($tz !== 'UTC' || !in_array($this->_timeFormat, [ 'c', 'Y-m-d\TH:i:sO', 'Y-m-d\TH:i:sP', 'Y-m-d\TH:i:s\Z' ])) {
- $n = ($tz !== 'UTC') ? $now->setTimezone(new \DateTimeZone('UTC')) : $now;
- $time->setAttribute('datetime', $n->format('Y-m-d\TH:i:s\Z'));
+ if (isset($frame['args'])) {
+ $pre = $this->_document->createElement('pre');
+ $pre->appendChild($this->_document->createTextNode(trim(print_r($frame['args'], true))));
+ $li->appendChild($pre);
+ }
}
- $time->appendChild($this->_document->createTextNode($now->format($this->_timeFormat)));
- $p->appendChild($time);
- $frag->appendChild($p);
- $ip = $this->_document->createElement('div');
- $frag->appendChild($ip);
+ $hasSiblings = true;
+ }
+
+ if ($hasSiblings) {
+ $p = $this->_document->createElement('p');
+ $p->appendChild($tFrag);
+ $ip->insertBefore($p, $ip->firstChild);
} else {
- $ip = $frag;
+ $ip->appendChild($tFrag);
}
- $p = $this->_document->createElement('p');
- $p->appendChild($this->buildThrowable($controller));
- $ip->appendChild($p);
-
- if ($this->_outputPrevious) {
- $prev = $controller->getPrevious();
- if ($prev !== null) {
- $ul = $this->_document->createElement('ul');
- $ip->appendChild($ul);
- $f = null;
- while ($prev) {
- if ($f !== null) {
- $p = $this->_document->createElement('p');
- $p->appendChild($f);
- $li->appendChild($p);
- $ul = $this->_document->createElement('ul');
- $li->appendChild($ul);
- }
+ return $frag;
+ }
- $li = $this->_document->createElement('li');
- $ul->appendChild($li);
- $f = $this->_document->createDocumentFragment();
- $span = $this->_document->createElement('span');
- $span->appendChild($this->_document->createTextNode('Caused by:'));
- $f->appendChild($span);
- $f->appendChild($this->_document->createTextNode(' '));
- $f->appendChild($this->buildThrowable($prev));
+ protected function dispatchCallback(): void {
+ $frag = $this->_document->createDocumentFragment();
+ $allSilent = true;
+ foreach ($this->outputBuffer as $o) {
+ if ($o['outputCode'] & self::SILENT) {
+ continue;
+ }
- $prev = $prev->getPrevious();
- }
+ $li = $this->_document->createElement('li');
+ $li->appendChild($this->buildOutputThrowable($o));
+ $frag->appendChild($li);
- $li->appendChild($f);
- }
+ $allSilent = false;
}
- if ($this->_outputBacktrace) {
- $frames = $controller->getFrames();
- if (count($frames) > 0) {
- $p = $this->_document->createElement('p');
- $p->appendChild($this->_document->createTextNode('Stack trace:'));
- $ip->appendChild($p);
-
- $ol = $this->_document->createElement('ol');
- $ip->appendChild($ol);
-
- $num = 0;
- foreach ($frames as $frame) {
- $li = $this->_document->createElement('li');
- $ol->appendChild($li);
-
- $args = (isset($frame['args']) && $this->_backtraceArgFrameLimit >= ++$num);
- if ($args) {
- $t = $this->_document->createElement('p');
- $li->appendChild($t);
- } else {
- $t = $li;
- }
-
- $b = $this->_document->createElement('b');
- $code = $this->_document->createElement('code');
- $b->appendChild($code);
- $t->appendChild($b);
-
- $text = $frame['error'] ?? $frame['class'] ?? '';
- if (isset($frame['function'])) {
- $text = ((isset($frame['class'])) ? '::' : '') . "{$frame['function']}()";
- }
- $code->appendChild($this->_document->createTextNode($text));
-
- $t->appendChild($this->_document->createTextNode("\u{00a0}\u{00a0}"));
- $code = $this->_document->createElement('code');
- $code->appendChild($this->_document->createTextNode($frame['file']));
- $t->appendChild($code);
- $t->appendChild($this->_document->createTextNode(":{$frame['line']}"));
-
- if ($args) {
- $pre = $this->_document->createElement('pre');
- $pre->appendChild($this->_document->createTextNode(print_r($frame['args'], true)));
- $li->appendChild($pre);
- }
- }
- }
+ if (!$allSilent) {
+ $ul = $this->_document->createElement('ul');
+ $ul->appendChild($frag);
+ $this->errorLocation->appendChild($ul);
+ $this->print($this->serializeDocument());
}
+ }
- return new HandlerOutput($this->getControlCode(), $this->getOutputCode(), $frag);
+ protected function serializeDocument() {
+ return $this->_document->saveHTML();
}
}
\ No newline at end of file
diff --git a/lib/Catcher/Handler.php b/lib/Catcher/Handler.php
index 2930c1c..0ad9ba6 100644
--- a/lib/Catcher/Handler.php
+++ b/lib/Catcher/Handler.php
@@ -55,10 +55,14 @@ abstract class Handler {
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; defaults to true */
+ protected bool $_outputTime = true;
/** When the SAPI is cli output errors to stderr; defaults to true */
protected bool $_outputToStderr = true;
/** If true the handler will be silent and won't output */
protected bool $_silent = false;
+ /** The PHP-standard date format which to use for timestamps in output */
+ protected string $_timeFormat = 'Y-m-d\TH:i:s.vO';
@@ -67,7 +71,7 @@ abstract class Handler {
foreach ($options as $key => $value) {
$key = "_$key";
if ($key === '_httpCode' && is_int($value) && $value !== 200 && max(400, min($value, 600)) !== $value) {
- throw new \InvalidArgumentException('Option "httpCode" can only be an integer of 200 or 400-599');
+ throw new \RangeException('Option "httpCode" can only be an integer of 200 or 400-599');
}
$this->$key = $value;
@@ -110,8 +114,35 @@ abstract class Handler {
return $this->$name;
}
- public function handle(ThrowableController $controller): HandlerOutput {
- $output = $this->handleCallback($controller);
+ public function handle(ThrowableController $controller): array {
+ $output = $this->buildOutputArray($controller);
+
+ if ($this->_outputBacktrace) {
+ $output['frames'] = $controller->getFrames(argFrameLimit: $this->_backtraceArgFrameLimit);
+ }
+ if ($this->_outputTime && $this->_timeFormat !== '') {
+ $output['time'] = new \DateTimeImmutable();
+ }
+
+ $code = self::CONTINUE;
+ if ($this->_forceBreak) {
+ $code = self::BREAK;
+ }
+ if ($this->_forceExit) {
+ $code |= self::EXIT;
+ }
+ $output['controlCode'] = $code;
+
+ $code = self::OUTPUT;
+ if ($this->_silent) {
+ $code = self::SILENT;
+ }
+ if ($this->_forceOutputNow) {
+ $code |= self::NOW;
+ }
+ $output['outputCode'] = $code;
+
+ $output = $this->handleCallback($output);
$this->outputBuffer[] = $output;
return $output;
}
@@ -127,33 +158,52 @@ abstract class Handler {
}
- abstract protected function dispatchCallback(): void;
+ protected function buildOutputArray(ThrowableController $controller): array {
+ $throwable = $controller->getThrowable();
- protected function getControlCode(): int {
- $code = self::CONTINUE;
- if ($this->_forceBreak) {
- $code = self::BREAK;
+ $output = [
+ 'controller' => $controller,
+ 'class' => $throwable::class,
+ 'code' => $throwable->getCode(),
+ 'file' => $throwable->getFile() ?: '[UNKNOWN]',
+ 'line' => $throwable->getLine(),
+ 'message' => $throwable->getMessage()
+ ];
+
+ if ($throwable instanceof Error) {
+ $output['errorType'] = $controller->getErrorType();
}
- if ($this->_forceExit) {
- $code |= self::EXIT;
+
+ if ($this->_outputPrevious) {
+ $prevController = $controller->getPrevious();
+ if ($prevController) {
+ $output['previous'] = $this->buildOutputArray($prevController);
+ }
}
-
- return $code;
+
+ return $output;
}
- protected function getOutputCode(): int {
- $code = self::OUTPUT;
- if ($this->_silent) {
- $code = self::SILENT;
+ protected function cleanOutputThrowable(array $outputThrowable): array {
+ unset($outputThrowable['controller']);
+ unset($outputThrowable['controlCode']);
+ unset($outputThrowable['outputCode']);
+
+ if (isset($outputThrowable['previous'])) {
+ $outputThrowable['previous'] = $this->cleanOutputThrowable($outputThrowable['previous']);
}
- if ($this->_forceOutputNow) {
- $code |= self::NOW;
+ if (isset($outputThrowable['time'])) {
+ $outputThrowable['time'] = $outputThrowable['time']->format($this->_timeFormat);
}
- return $code;
+ return $outputThrowable;
}
- abstract protected function handleCallback(ThrowableController $controller): HandlerOutput;
+ abstract protected function dispatchCallback(): void;
+
+ protected function handleCallback(array $output): array {
+ return $output;
+ }
protected function print(string $string): void {
$string = "$string\n";
diff --git a/lib/Catcher/HandlerOutput.php b/lib/Catcher/HandlerOutput.php
deleted file mode 100644
index 68399fe..0000000
--- a/lib/Catcher/HandlerOutput.php
+++ /dev/null
@@ -1,23 +0,0 @@
-controlCode = $controlCode;
- $this->outputCode = $outputCode;
- $this->output = $output;
- }
-}
\ No newline at end of file
diff --git a/lib/Catcher/JSONHandler.php b/lib/Catcher/JSONHandler.php
index de56e8f..2fef98c 100644
--- a/lib/Catcher/JSONHandler.php
+++ b/lib/Catcher/JSONHandler.php
@@ -7,112 +7,26 @@
declare(strict_types=1);
namespace MensBeam\Foundation\Catcher;
-use \Psr\Log\LoggerInterface;
class JSONHandler extends Handler {
public const CONTENT_TYPE = 'application/json';
- /** If true the handler will output times to the output; defaults to true */
- protected bool $_outputTime = true;
- /** The PHP-standard date format which to use for timestamps in output */
- protected string $_timeFormat = 'c';
-
protected function dispatchCallback(): void {
- $output = [
- 'status' => (string)$this->_httpCode
- ];
-
- $errors = [];
- foreach ($this->outputBuffer as $o) {
- if ($o->outputCode & self::SILENT) {
+ foreach ($this->outputBuffer as $key => $value) {
+ if ($value['outputCode'] & self::SILENT) {
+ unset($this->outputBuffer[$key]);
continue;
}
- $errors[] = $o->output;
- }
- if (count($errors) === 0) {
- return;
- }
-
- $output['errors'] = $errors;
- $this->print(json_encode($output, \JSON_PARTIAL_OUTPUT_ON_ERROR));
- }
-
- protected function handleCallback(ThrowableController $controller): HandlerOutput {
- $output = $this->buildThrowableArray($controller);
- if ($this->_outputPrevious) {
- $target = $output;
- $prevController = $controller->getPrevious();
- while ($prevController) {
- $prev = $this->buildThrowableArray($prevController);
- $target['previous'] = $prev;
- $target = $prev;
- $prevController = $prevController->getPrevious();
- }
- }
-
- if ($this->_outputBacktrace) {
- $output['frames'] = $controller->getFrames();
- }
-
- if ($this->_outputTime && $this->_timeFormat !== '') {
- $output['timestamp'] = (new \DateTime())->format($this->_timeFormat);
- }
-
- return new HandlerOutput($this->getControlCode(), $this->getOutputCode(), $output);
- }
-
-
-
- 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;
- default: $this->_logger->critical($message);
- }
- } elseif ($throwable instanceof \Exception && ($throwable instanceof \PharException || $throwable instanceof \RuntimeException)) {
- $this->_logger->alert($message);
- } else {
- $this->_logger->critical($message);
+ $this->outputBuffer[$key] = $this->cleanOutputThrowable($this->outputBuffer[$key]);
}
- }
- protected function buildThrowableArray(ThrowableController $controller): array {
- $throwable = $controller->getThrowable();
- $type = $throwable::class;
- if ($throwable instanceof Error) {
- $t = $controller->getErrorType();
- $t = ($throwable instanceof Error) ? $controller->getErrorType() : null;
- $type = ($t !== null) ? "$t (" . $type . ")" : $type;
+ if (count($this->outputBuffer) > 0) {
+ $this->print(json_encode([
+ 'errors' => $this->outputBuffer
+ ], \JSON_INVALID_UTF8_SUBSTITUTE | \JSON_PARTIAL_OUTPUT_ON_ERROR | \JSON_UNESCAPED_SLASHES));
}
-
- return [
- 'code' => $throwable->getCode(),
- 'file' => $throwable->getFile(),
- 'line' => $throwable->getLine(),
- 'message' => $throwable->getMessage(),
- 'type' => $type
- ];
}
}
\ No newline at end of file
diff --git a/lib/Catcher/PlainTextHandler.php b/lib/Catcher/PlainTextHandler.php
index f7a84a7..02178c5 100644
--- a/lib/Catcher/PlainTextHandler.php
+++ b/lib/Catcher/PlainTextHandler.php
@@ -15,91 +15,26 @@ class PlainTextHandler extends Handler {
/** 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 times to the output; defaults to true */
- protected bool $_outputTime = true;
/** The PHP-standard date format which to use for timestamps in output */
protected string $_timeFormat = '[H:i:s]';
-
protected function dispatchCallback(): void {
foreach ($this->outputBuffer as $o) {
- if ($o->outputCode & self::SILENT) {
+ if ($o['outputCode'] & self::SILENT) {
continue;
}
- $this->print($o->output);
+ $this->print($this->serializeOutputThrowable($o));
}
}
- protected function handleCallback(ThrowableController $controller): HandlerOutput {
- $output = $this->serializeThrowable($controller);
- if ($this->_outputPrevious) {
- $prevController = $controller->getPrevious();
- $indent = '';
- while ($prevController) {
- $output .= sprintf("\n%s↳ %s", $indent, $this->serializeThrowable($prevController));
- $prevController = $prevController->getPrevious();
- $indent .= ' ';
- }
- }
-
- if ($this->_outputBacktrace) {
- $frames = $controller->getFrames();
- $output .= "\n\nStack trace:";
-
- $num = 1;
- $maxDigits = strlen((string)count($frames));
- 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(' ', $maxDigits) . '| ', print_r($frame['args'], true));
- }
-
- $template = "\n%{$maxDigits}d. %s";
- if ($class && $function) {
- $template .= '::';
- }
- $template .= ($function) ? '%s()' : '%s';
- $template .= ' %s:%d%s';
-
- $output .= sprintf(
- "$template\n",
- $num++,
- $class,
- $function,
- $frame['file'],
- $frame['line'],
- $args
- );
- }
-
- $output = rtrim($output, "\n");
- }
-
- // The logger will handle timestamps itself.
- if ($this->_logger !== null) {
- $this->log($controller->getThrowable(), $output);
- }
-
- if (!$this->_silent && $this->_outputTime && $this->_timeFormat !== '') {
- $time = (new \DateTime())->format($this->_timeFormat) . ' ';
- $timeStrlen = strlen($time);
-
- $output = preg_replace('/^/m', str_repeat(' ', $timeStrlen), $output);
- $output = preg_replace('/^ {' . $timeStrlen . '}/', $time, $output);
- }
-
- $outputCode = $this->getOutputCode();
- return new HandlerOutput($this->getControlCode(), (\PHP_SAPI === 'cli') ? $outputCode | self::NOW : $outputCode, $output);
+ protected function handleCallback(array $output): array {
+ $output['outputCode'] = (\PHP_SAPI === 'cli') ? $output['outputCode'] | self::NOW : $output['outputCode'];
+ return $output;
}
-
-
protected function log(\Throwable $throwable, string $message): void {
if ($throwable instanceof \Error) {
switch ($throwable->getCode()) {
@@ -132,21 +67,72 @@ class PlainTextHandler extends Handler {
}
}
- protected function serializeThrowable(ThrowableController $controller): string {
- $throwable = $controller->getThrowable();
- $class = $throwable::class;
- if ($throwable instanceof Error) {
- $type = $controller->getErrorType();
- $type = ($throwable instanceof Error) ? $controller->getErrorType() : null;
- $class = ($type !== null) ? "$type (" . $throwable::class . ")" : $throwable::class;
+ protected function serializeOutputThrowable(array $outputThrowable, bool $previous = false): string {
+ $class = $outputThrowable['class'] ?? null;
+ if ($class !== null && !empty($outputThrowable['errorType'])) {
+ $class = "{$outputThrowable['errorType']} ($class)";
}
-
- return sprintf(
- '%s: %s in file %s on line %d',
+
+ $output = sprintf(
+ '%s: %s in file %s on line %d' . \PHP_EOL,
$class,
- $throwable->getMessage(),
- $throwable->getFile(),
- $throwable->getLine()
+ $outputThrowable['message'],
+ $outputThrowable['file'],
+ $outputThrowable['line']
);
+
+ if (!empty($outputThrowable['previous'])) {
+ if ($previous) {
+ $output .= ' ';
+ }
+ $output .= '↳ ' . $this->serializeOutputThrowable($outputThrowable['previous'], true);
+ }
+
+ if (!$previous) {
+ if (isset($outputThrowable['frames']) && count($outputThrowable['frames']) > 0) {
+ $output .= \PHP_EOL . 'Stack trace:' . \PHP_EOL;
+ $maxDigits = strlen((string)count($outputThrowable['frames']));
+ $indent = str_repeat(' ', $maxDigits);
+ foreach ($outputThrowable['frames'] as $key => $frame) {
+ $method = null;
+ if (!empty($frame['class'])) {
+ if (!empty($frame['errorType'])) {
+ $method = "{$frame['errorType']} ({$frame['class']})";
+ } else {
+ $method = $frame['class'];
+ if (!empty($frame['function'])) {
+ $method .= "::{$frame['function']}";
+ }
+ }
+ } elseif (!empty($frame['function'])) {
+ $method = $frame['function'];
+ }
+
+ $output .= sprintf("%{$maxDigits}d. %s %s:%d" . \PHP_EOL,
+ $key + 1,
+ $method,
+ $frame['file'],
+ $frame['line']
+ );
+
+ if (!empty($frame['args']) && $this->_backtraceArgFrameLimit > $key) {
+ $output .= preg_replace('/^/m', "$indent| ", print_r($frame['args'], true)) . \PHP_EOL;
+ }
+ }
+
+ $output = rtrim($output) . \PHP_EOL;
+ }
+
+ if (!empty($this->_logger)) {
+ $this->log($outputThrowable['controller']->getThrowable(), $output);
+ }
+
+ if (!empty($outputThrowable['time'])) {
+ $timestamp = $outputThrowable['time']->format($this->_timeFormat) . ' ';
+ $output = ltrim(preg_replace('/^/m', str_repeat(' ', strlen($timestamp)), "$timestamp$output"));
+ }
+ }
+
+ return $output;
}
}
\ No newline at end of file
diff --git a/lib/Catcher/ThrowableController.php b/lib/Catcher/ThrowableController.php
index 688775c..fa985be 100644
--- a/lib/Catcher/ThrowableController.php
+++ b/lib/Catcher/ThrowableController.php
@@ -88,10 +88,13 @@ class ThrowableController {
}
/** Gets backtrace frames */
- public function getFrames(): array {
+ public function getFrames(int $argFrameLimit = \PHP_INT_MAX): array {
if ($this->frames !== null) {
return $this->frames;
}
+ if ($argFrameLimit < 0) {
+ throw new \RangeException('Argument argFrameLimit cannot be less than 0');
+ }
if (
!$this->throwable instanceof \Error ||
@@ -149,7 +152,7 @@ class ThrowableController {
// Add a frame for the throwable to the beginning of the array
$f = [
- 'file' => $this->throwable->getFile(),
+ 'file' => $this->throwable->getFile() ?: '[UNKNOWN]',
'line' => (int)$this->throwable->getLine(),
'class' => $this->throwable::class,
'args' => [
@@ -159,10 +162,10 @@ class ThrowableController {
// Add the error code and type if it is an Error.
if ($this->throwable instanceof \Error) {
- $error = $this->getErrorType();
- if ($error !== null) {
+ $errorType = $this->getErrorType();
+ if ($errorType !== null) {
$f['code'] = $this->throwable->getCode();
- $f['type'] = $error;
+ $f['errorType'] = $errorType;
}
}
@@ -187,6 +190,13 @@ class ThrowableController {
$frames = $temp;
}
+ // Lastly, remove all args past the specified limit.
+ if ($argFrameLimit !== \PHP_INT_MAX) {
+ for ($i = $argFrameLimit, $frameCount = count($frames); $i < $frameCount; $i++) {
+ unset($frames[$i]['args']);
+ }
+ }
+
$this->frames = $frames;
return $frames;
}
diff --git a/tests/cases/TestCatcher.php b/tests/cases/TestCatcher.php
index 53192d9..55644a5 100644
--- a/tests/cases/TestCatcher.php
+++ b/tests/cases/TestCatcher.php
@@ -16,10 +16,7 @@ use MensBeam\Foundation\Catcher\{
};
use Eloquent\Phony\Phpunit\Phony;
-/**
- * @runTestsInSeparateProcesses
- * @preserveGlobalState disabled
- */
+
class TestCatcher extends \PHPUnit\Framework\TestCase {
/**
* @covers \MensBeam\Foundation\Catcher::__construct
@@ -35,7 +32,6 @@ class TestCatcher extends \PHPUnit\Framework\TestCase {
$c = new Catcher();
$c->preventExit = true;
$c->throwErrors = false;
- $this->assertSame('MensBeam\Foundation\Catcher', $c::class);
$this->assertSame(1, count($c->getHandlers()));
$this->assertSame(PlainTextHandler::class, $c->getHandlers()[0]::class);
$c->unregister();
@@ -67,15 +63,13 @@ class TestCatcher extends \PHPUnit\Framework\TestCase {
* @covers \MensBeam\Foundation\Catcher::unregister
* @covers \MensBeam\Foundation\Catcher\Error::__construct
* @covers \MensBeam\Foundation\Catcher\Handler::__construct
+ * @covers \MensBeam\Foundation\Catcher\Handler::buildOutputArray
* @covers \MensBeam\Foundation\Catcher\Handler::dispatch
- * @covers \MensBeam\Foundation\Catcher\Handler::getControlCode
- * @covers \MensBeam\Foundation\Catcher\Handler::getOutputCode
* @covers \MensBeam\Foundation\Catcher\Handler::handle
- * @covers \MensBeam\Foundation\Catcher\HandlerOutput::__construct
* @covers \MensBeam\Foundation\Catcher\PlainTextHandler::dispatchCallback
* @covers \MensBeam\Foundation\Catcher\PlainTextHandler::handleCallback
- * @covers \MensBeam\Foundation\Catcher\PlainTextHandler::serializeThrowable
* @covers \MensBeam\Foundation\Catcher\ThrowableController::__construct
+ * @covers \MensBeam\Foundation\Catcher\ThrowableController::getErrorType
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getPrevious
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getThrowable
*/
@@ -316,22 +310,19 @@ class TestCatcher extends \PHPUnit\Framework\TestCase {
*
* @covers \MensBeam\Foundation\Catcher::__construct
* @covers \MensBeam\Foundation\Catcher::getLastThrowable
- * @covers \MensBeam\Foundation\Catcher::exit
* @covers \MensBeam\Foundation\Catcher::handleThrowable
* @covers \MensBeam\Foundation\Catcher::pushHandler
* @covers \MensBeam\Foundation\Catcher::register
* @covers \MensBeam\Foundation\Catcher::unregister
* @covers \MensBeam\Foundation\Catcher\Error::__construct
* @covers \MensBeam\Foundation\Catcher\Handler::__construct
+ * @covers \MensBeam\Foundation\Catcher\Handler::buildOutputArray
* @covers \MensBeam\Foundation\Catcher\Handler::dispatch
- * @covers \MensBeam\Foundation\Catcher\Handler::getControlCode
- * @covers \MensBeam\Foundation\Catcher\Handler::getOutputCode
* @covers \MensBeam\Foundation\Catcher\Handler::handle
- * @covers \MensBeam\Foundation\Catcher\HandlerOutput::__construct
* @covers \MensBeam\Foundation\Catcher\PlainTextHandler::dispatchCallback
* @covers \MensBeam\Foundation\Catcher\PlainTextHandler::handleCallback
- * @covers \MensBeam\Foundation\Catcher\PlainTextHandler::serializeThrowable
* @covers \MensBeam\Foundation\Catcher\ThrowableController::__construct
+ * @covers \MensBeam\Foundation\Catcher\ThrowableController::getErrorType
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getPrevious
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getThrowable
*/
@@ -409,14 +400,11 @@ class TestCatcher extends \PHPUnit\Framework\TestCase {
* @covers \MensBeam\Foundation\Catcher::unregister
* @covers \MensBeam\Foundation\Catcher\Error::__construct
* @covers \MensBeam\Foundation\Catcher\Handler::__construct
+ * @covers \MensBeam\Foundation\Catcher\Handler::buildOutputArray
* @covers \MensBeam\Foundation\Catcher\Handler::dispatch
- * @covers \MensBeam\Foundation\Catcher\Handler::getControlCode
- * @covers \MensBeam\Foundation\Catcher\Handler::getOutputCode
* @covers \MensBeam\Foundation\Catcher\Handler::handle
- * @covers \MensBeam\Foundation\Catcher\HandlerOutput::__construct
* @covers \MensBeam\Foundation\Catcher\PlainTextHandler::dispatchCallback
* @covers \MensBeam\Foundation\Catcher\PlainTextHandler::handleCallback
- * @covers \MensBeam\Foundation\Catcher\PlainTextHandler::serializeThrowable
* @covers \MensBeam\Foundation\Catcher\ThrowableController::__construct
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getPrevious
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getThrowable
@@ -456,14 +444,11 @@ class TestCatcher extends \PHPUnit\Framework\TestCase {
* @covers \MensBeam\Foundation\Catcher::unregister
* @covers \MensBeam\Foundation\Catcher\Error::__construct
* @covers \MensBeam\Foundation\Catcher\Handler::__construct
+ * @covers \MensBeam\Foundation\Catcher\Handler::buildOutputArray
* @covers \MensBeam\Foundation\Catcher\Handler::dispatch
- * @covers \MensBeam\Foundation\Catcher\Handler::getControlCode
- * @covers \MensBeam\Foundation\Catcher\Handler::getOutputCode
* @covers \MensBeam\Foundation\Catcher\Handler::handle
- * @covers \MensBeam\Foundation\Catcher\HandlerOutput::__construct
* @covers \MensBeam\Foundation\Catcher\PlainTextHandler::dispatchCallback
* @covers \MensBeam\Foundation\Catcher\PlainTextHandler::handleCallback
- * @covers \MensBeam\Foundation\Catcher\PlainTextHandler::serializeThrowable
* @covers \MensBeam\Foundation\Catcher\ThrowableController::__construct
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getPrevious
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getThrowable
diff --git a/tests/cases/TestHTMLHandler.php b/tests/cases/TestHTMLHandler.php
index 05c3b8a..915b99b 100644
--- a/tests/cases/TestHTMLHandler.php
+++ b/tests/cases/TestHTMLHandler.php
@@ -15,10 +15,7 @@ use MensBeam\Foundation\Catcher\{
ThrowableController
};
-/**
- * @runTestsInSeparateProcesses
- * @preserveGlobalState disabled
- */
+
class TestHTMLHandler extends \PHPUnit\Framework\TestCase {
/**
* @covers \MensBeam\Foundation\Catcher\HTMLHandler::__construct
@@ -31,67 +28,62 @@ class TestHTMLHandler extends \PHPUnit\Framework\TestCase {
}
/**
- * @covers \MensBeam\Foundation\Catcher\HTMLHandler::buildThrowable
+ * @covers \MensBeam\Foundation\Catcher\HTMLHandler::buildOutputThrowable
*
* @covers \MensBeam\Foundation\Catcher\Error::__construct
* @covers \MensBeam\Foundation\Catcher\Handler::__construct
- * @covers \MensBeam\Foundation\Catcher\Handler::getControlCode
- * @covers \MensBeam\Foundation\Catcher\Handler::getOutputCode
+ * @covers \MensBeam\Foundation\Catcher\Handler::buildOutputArray
* @covers \MensBeam\Foundation\Catcher\Handler::handle
- * @covers \MensBeam\Foundation\Catcher\HandlerOutput::__construct
* @covers \MensBeam\Foundation\Catcher\HTMLHandler::__construct
+ * @covers \MensBeam\Foundation\Catcher\HTMLHandler::dispatchCallback
* @covers \MensBeam\Foundation\Catcher\HTMLHandler::handleCallback
+ * @covers \MensBeam\Foundation\Catcher\HTMLHandler::serializeDocument
* @covers \MensBeam\Foundation\Catcher\ThrowableController::__construct
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getErrorType
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getFrames
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getPrevious
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getThrowable
*/
- public function testMethod_buildThrowable(): void {
+ public function testMethod_buildOutputThrowable(): void {
$c = new ThrowableController(new \Exception(message: 'Ook!', previous: new Error(message: 'Eek!', code: \E_USER_ERROR, previous: new Error(message: 'Ack!'))));
$h = new HTMLHandler([
+ 'backtraceArgFrameLimit' => 1,
'outputBacktrace' => true,
- 'outputTime' => false
+ 'outputToStderr' => false
]);
$o = $h->handle($c);
- $this->assertSame(Handler::CONTINUE, $o->controlCode);
- $this->assertInstanceOf(\DOMDocumentFragment::class, $o->output);
+ $this->assertSame(Handler::CONTINUE, $o['controlCode']);
+
+ ob_start();
+ $h->dispatch();
+ ob_end_clean();
}
/**
* @covers \MensBeam\Foundation\Catcher\HTMLHandler::dispatchCallback
*
- * @covers \MensBeam\Foundation\Catcher\Error::__construct
* @covers \MensBeam\Foundation\Catcher\Handler::__construct
+ * @covers \MensBeam\Foundation\Catcher\Handler::buildOutputArray
* @covers \MensBeam\Foundation\Catcher\Handler::dispatch
- * @covers \MensBeam\Foundation\Catcher\Handler::getControlCode
- * @covers \MensBeam\Foundation\Catcher\Handler::getOutputCode
* @covers \MensBeam\Foundation\Catcher\Handler::handle
- * @covers \MensBeam\Foundation\Catcher\Handler::print
- * @covers \MensBeam\Foundation\Catcher\Handler::setOption
- * @covers \MensBeam\Foundation\Catcher\HandlerOutput::__construct
+ * @covers \MensBeam\Foundation\Catcher\Handler::handleCallback
* @covers \MensBeam\Foundation\Catcher\HTMLHandler::__construct
- * @covers \MensBeam\Foundation\Catcher\HTMLHandler::buildThrowable
- * @covers \MensBeam\Foundation\Catcher\HTMLHandler::handleCallback
* @covers \MensBeam\Foundation\Catcher\ThrowableController::__construct
- * @covers \MensBeam\Foundation\Catcher\ThrowableController::getErrorType
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getPrevious
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getThrowable
*/
public function testMethod_dispatchCallback(): void {
- $c = new ThrowableController(new \Exception(message: 'Ook!', previous: new Error(message: 'Eek!', code: \E_USER_ERROR, previous: new \Error(message: 'Ack!'))));
+ $c = new ThrowableController(new \Exception(message: 'Ook!'));
$h = new HTMLHandler([
- 'outputToStderr' => false
+ 'backtraceArgFrameLimit' => 1,
+ 'outputToStderr' => false,
+ 'silent' => true
]);
$h->handle($c);
ob_start();
$h->dispatch();
$o = ob_get_clean();
- $this->assertNotNull($o);
-
- $h->setOption('silent', true);
- $h->handle($c);
- $h->dispatch();
+ $this->assertEmpty($o);
}
}
\ No newline at end of file
diff --git a/tests/cases/TestHandler.php b/tests/cases/TestHandler.php
index 9d3bfff..c7c38ac 100644
--- a/tests/cases/TestHandler.php
+++ b/tests/cases/TestHandler.php
@@ -11,55 +11,74 @@ use MensBeam\Foundation\Catcher;
use MensBeam\Foundation\Catcher\{
Error,
HTMLHandler,
+ PlainTextHandler,
JSONHandler,
- PlainTextHandler
+ ThrowableController
};
use Eloquent\Phony\Phpunit\Phony;
-/**
- * @runTestsInSeparateProcesses
- * @preserveGlobalState disabled
- */
+
class TestHandler extends \PHPUnit\Framework\TestCase {
/**
* @covers \MensBeam\Foundation\Catcher\Handler::__construct
*/
public function testMethod___construct__exception(): void {
- $this->expectException(\InvalidArgumentException::class);
+ $this->expectException(\RangeException::class);
new PlainTextHandler([ 'httpCode' => 42 ]);
}
/**
- * @covers \MensBeam\Foundation\Catcher\Handler::getControlCode
+ * @covers \MensBeam\Foundation\Catcher\Handler::cleanOutputThrowable
*
- * @covers \MensBeam\Foundation\Catcher::__construct
- * @covers \MensBeam\Foundation\Catcher::handleError
- * @covers \MensBeam\Foundation\Catcher::handleThrowable
- * @covers \MensBeam\Foundation\Catcher::pushHandler
- * @covers \MensBeam\Foundation\Catcher::register
- * @covers \MensBeam\Foundation\Catcher::unregister
- * @covers \MensBeam\Foundation\Catcher\Error::__construct
* @covers \MensBeam\Foundation\Catcher\Handler::__construct
+ * @covers \MensBeam\Foundation\Catcher\Handler::buildOutputArray
* @covers \MensBeam\Foundation\Catcher\Handler::dispatch
* @covers \MensBeam\Foundation\Catcher\Handler::handle
- * @covers \MensBeam\Foundation\Catcher\Handler::getOutputCode
- * @covers \MensBeam\Foundation\Catcher\HandlerOutput::__construct
- * @covers \MensBeam\Foundation\Catcher\PlainTextHandler::dispatchCallback
- * @covers \MensBeam\Foundation\Catcher\PlainTextHandler::handleCallback
- * @covers \MensBeam\Foundation\Catcher\PlainTextHandler::serializeThrowable
+ * @covers \MensBeam\Foundation\Catcher\Handler::handleCallback
+ * @covers \MensBeam\Foundation\Catcher\Handler::print
+ * @covers \MensBeam\Foundation\Catcher\JSONHandler::dispatchCallback
* @covers \MensBeam\Foundation\Catcher\ThrowableController::__construct
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getErrorType
+ * @covers \MensBeam\Foundation\Catcher\ThrowableController::getFrames
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getPrevious
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getThrowable
*/
- public function testMethod__getControlCode(): void {
- // Just need to test forceExit for coverage purposes
- $c = new Catcher(new PlainTextHandler([ 'forceExit' => true, 'silent' => true ]));
- $c->preventExit = true;
- $c->throwErrors = false;
- trigger_error('Ook!', \E_USER_ERROR);
- $this->assertSame(\E_USER_ERROR, $c->getLastThrowable()->getCode());
- $c->unregister();
+ public function testMethod__cleanOutputThrowable(): void {
+ // Just need to test coverage here; TestJSONHandler covers this one thoroughly.
+ $c = new ThrowableController(new \Exception(message: 'Ook!', previous: new \Error('Eek!')));
+ $h = new JSONHandler([
+ 'outputBacktrace' => true,
+ 'outputToStderr' => false
+ ]);
+ $o = $h->handle($c);
+
+ ob_start();
+ $h->dispatch();
+ ob_end_clean();
+
+ $this->assertTrue(isset($o['frames']));
+ }
+
+ /**
+ * @covers \MensBeam\Foundation\Catcher\Handler::handle
+ *
+ * @covers \MensBeam\Foundation\Catcher\Handler::__construct
+ * @covers \MensBeam\Foundation\Catcher\Handler::buildOutputArray
+ * @covers \MensBeam\Foundation\Catcher\Handler::handleCallback
+ * @covers \MensBeam\Foundation\Catcher\ThrowableController::__construct
+ * @covers \MensBeam\Foundation\Catcher\ThrowableController::getErrorType
+ * @covers \MensBeam\Foundation\Catcher\ThrowableController::getFrames
+ * @covers \MensBeam\Foundation\Catcher\ThrowableController::getPrevious
+ * @covers \MensBeam\Foundation\Catcher\ThrowableController::getThrowable
+ */
+ public function testMethod__handle(): void {
+ // Just need to test backtrace handling. The rest has already been covered by prior tests.
+ $c = new ThrowableController(new \Exception(message: 'Ook!', previous: new \Error(message: 'Eek!', previous: new Error(message: 'Eek!', code: \E_USER_ERROR))));
+ $h = new HTMLHandler([
+ 'outputBacktrace' => true,
+ 'outputToStderr' => true
+ ]);
+ $this->assertTrue(isset($h->handle($c)['frames']));
}
/**
@@ -74,13 +93,8 @@ class TestHandler extends \PHPUnit\Framework\TestCase {
* @covers \MensBeam\Foundation\Catcher\Error::__construct
* @covers \MensBeam\Foundation\Catcher\Handler::__construct
* @covers \MensBeam\Foundation\Catcher\Handler::dispatch
- * @covers \MensBeam\Foundation\Catcher\Handler::getControlCode
- * @covers \MensBeam\Foundation\Catcher\Handler::getOutputCode
* @covers \MensBeam\Foundation\Catcher\Handler::handle
- * @covers \MensBeam\Foundation\Catcher\HandlerOutput::__construct
- * @covers \MensBeam\Foundation\Catcher\PlainTextHandler::dispatchCallback
* @covers \MensBeam\Foundation\Catcher\PlainTextHandler::handleCallback
- * @covers \MensBeam\Foundation\Catcher\PlainTextHandler::serializeThrowable
* @covers \MensBeam\Foundation\Catcher\ThrowableController::__construct
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getErrorType
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getPrevious
@@ -97,6 +111,11 @@ class TestHandler extends \PHPUnit\Framework\TestCase {
$c->unregister();
}
+ /**
+ * @covers \MensBeam\Foundation\Catcher\Handler::setOption
+ *
+ * @covers \MensBeam\Foundation\Catcher\Handler::__construct
+ */
public function testMethod__setOption(): void {
$h = new PlainTextHandler([ 'forceExit' => true, 'silent' => true ]);
$h->setOption('forceExit', false);
@@ -104,7 +123,6 @@ class TestHandler extends \PHPUnit\Framework\TestCase {
$r->setAccessible(true);
$this->assertFalse($r->getValue($h));
- //$h = Phony::partialMock(PlainTextHandler::class, [ [ 'silent' => true ] ]);
$m = Phony::partialMock(Catcher::class, [
$h
]);
@@ -118,39 +136,6 @@ class TestHandler extends \PHPUnit\Framework\TestCase {
$c->unregister();
}
- /**
- * @covers \MensBeam\Foundation\Catcher\Handler::getOutputCode
- *
- * @covers \MensBeam\Foundation\Catcher::__construct
- * @covers \MensBeam\Foundation\Catcher::handleError
- * @covers \MensBeam\Foundation\Catcher::handleThrowable
- * @covers \MensBeam\Foundation\Catcher::pushHandler
- * @covers \MensBeam\Foundation\Catcher::register
- * @covers \MensBeam\Foundation\Catcher::unregister
- * @covers \MensBeam\Foundation\Catcher\Error::__construct
- * @covers \MensBeam\Foundation\Catcher\Handler::__construct
- * @covers \MensBeam\Foundation\Catcher\Handler::dispatch
- * @covers \MensBeam\Foundation\Catcher\Handler::getControlCode
- * @covers \MensBeam\Foundation\Catcher\Handler::handle
- * @covers \MensBeam\Foundation\Catcher\HandlerOutput::__construct
- * @covers \MensBeam\Foundation\Catcher\PlainTextHandler::dispatchCallback
- * @covers \MensBeam\Foundation\Catcher\PlainTextHandler::handleCallback
- * @covers \MensBeam\Foundation\Catcher\PlainTextHandler::serializeThrowable
- * @covers \MensBeam\Foundation\Catcher\ThrowableController::__construct
- * @covers \MensBeam\Foundation\Catcher\ThrowableController::getErrorType
- * @covers \MensBeam\Foundation\Catcher\ThrowableController::getPrevious
- * @covers \MensBeam\Foundation\Catcher\ThrowableController::getThrowable
- */
- public function testMethod__getOutputCode(): void {
- // Just need to test forceOutputNow for coverage purposes
- $c = new Catcher(new PlainTextHandler([ 'forceOutputNow' => true, 'silent' => true ]));
- $c->preventExit = true;
- $c->throwErrors = false;
- trigger_error('Ook!', \E_USER_ERROR);
- $this->assertSame(\E_USER_ERROR, $c->getLastThrowable()->getCode());
- $c->unregister();
- }
-
/**
* @covers \MensBeam\Foundation\Catcher\Handler::print
*
@@ -163,13 +148,10 @@ class TestHandler extends \PHPUnit\Framework\TestCase {
* @covers \MensBeam\Foundation\Catcher\Error::__construct
* @covers \MensBeam\Foundation\Catcher\Handler::__construct
* @covers \MensBeam\Foundation\Catcher\Handler::dispatch
- * @covers \MensBeam\Foundation\Catcher\Handler::getControlCode
- * @covers \MensBeam\Foundation\Catcher\Handler::getOutputCode
* @covers \MensBeam\Foundation\Catcher\Handler::handle
- * @covers \MensBeam\Foundation\Catcher\HandlerOutput::__construct
* @covers \MensBeam\Foundation\Catcher\PlainTextHandler::dispatchCallback
* @covers \MensBeam\Foundation\Catcher\PlainTextHandler::handleCallback
- * @covers \MensBeam\Foundation\Catcher\PlainTextHandler::serializeThrowable
+ * @covers \MensBeam\Foundation\Catcher\PlainTextHandler::serializeOutputThrowable
* @covers \MensBeam\Foundation\Catcher\ThrowableController::__construct
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getErrorType
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getPrevious
diff --git a/tests/cases/TestJSONHandler.php b/tests/cases/TestJSONHandler.php
new file mode 100644
index 0000000..dd06bf8
--- /dev/null
+++ b/tests/cases/TestJSONHandler.php
@@ -0,0 +1,36 @@
+ true,
+ 'outputToStderr' => false
+ ]);
+ $o = $h->handle($c);
+
+ ob_start();
+ $h->dispatch();
+ $o = ob_get_clean();
+ $this->assertEmpty($o);
+ }
+}
\ No newline at end of file
diff --git a/tests/cases/TestPlainTextHandler.php b/tests/cases/TestPlainTextHandler.php
index 98dd7c9..1e16626 100644
--- a/tests/cases/TestPlainTextHandler.php
+++ b/tests/cases/TestPlainTextHandler.php
@@ -17,21 +17,18 @@ use MensBeam\Foundation\Catcher\{
use Eloquent\Phony\Phpunit\Phony,
Psr\Log\LoggerInterface;
-/**
- * @runTestsInSeparateProcesses
- * @preserveGlobalState disabled
- */
+
class TestPlainTextHandler extends \PHPUnit\Framework\TestCase {
/**
* @covers \MensBeam\Foundation\Catcher\PlainTextHandler::handleCallback
*
* @covers \MensBeam\Foundation\Catcher\Handler::__construct
- * @covers \MensBeam\Foundation\Catcher\Handler::getControlCode
- * @covers \MensBeam\Foundation\Catcher\Handler::getOutputCode
+ * @covers \MensBeam\Foundation\Catcher\Handler::buildOutputArray
* @covers \MensBeam\Foundation\Catcher\Handler::handle
- * @covers \MensBeam\Foundation\Catcher\HandlerOutput::__construct
+ * @covers \MensBeam\Foundation\Catcher\PlainTextHandler::dispatchCallback
+ * @covers \MensBeam\Foundation\Catcher\PlainTextHandler::handleCallback
* @covers \MensBeam\Foundation\Catcher\PlainTextHandler::log
- * @covers \MensBeam\Foundation\Catcher\PlainTextHandler::serializeThrowable
+ * @covers \MensBeam\Foundation\Catcher\PlainTextHandler::serializeOutputThrowable
* @covers \MensBeam\Foundation\Catcher\ThrowableController::__construct
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getErrorType
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getFrames
@@ -39,16 +36,22 @@ class TestPlainTextHandler extends \PHPUnit\Framework\TestCase {
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getThrowable
*/
public function testMethod_handleCallback(): void {
- $c = new ThrowableController(new \Exception(message: 'Ook!', previous: new \Error('Eek!')));
+ $c = new ThrowableController(new \Exception(message: 'Ook!', previous: new \Error(message: 'Eek!', previous: new Error(message: 'Ack!', code: \E_USER_ERROR))));
$l = Phony::mock(LoggerInterface::class);
$h = new PlainTextHandler([
'logger' => $l->get(),
- 'outputBacktrace' => true
+ 'outputBacktrace' => true,
+ 'outputToStderr' => false
]);
$o = $h->handle($c);
- $this->assertSame(Handler::CONTINUE, $o->controlCode);
- $this->assertSame(Handler::OUTPUT | Handler::NOW, $o->outputCode);
- $this->assertStringContainsString('↳', $o->output);
+ $this->assertSame(Handler::CONTINUE, $o['controlCode']);
+ $this->assertSame(Handler::OUTPUT | Handler::NOW, $o['outputCode']);
+ $this->assertTrue(isset($o['previous']));
+
+ ob_start();
+ $h->dispatch();
+ ob_end_clean();
+
$l->critical->called();
}
@@ -58,12 +61,11 @@ class TestPlainTextHandler extends \PHPUnit\Framework\TestCase {
*
* @covers \MensBeam\Foundation\Catcher\Error::__construct
* @covers \MensBeam\Foundation\Catcher\Handler::__construct
- * @covers \MensBeam\Foundation\Catcher\Handler::getControlCode
- * @covers \MensBeam\Foundation\Catcher\Handler::getOutputCode
+ * @covers \MensBeam\Foundation\Catcher\Handler::buildOutputArray
* @covers \MensBeam\Foundation\Catcher\Handler::handle
- * @covers \MensBeam\Foundation\Catcher\HandlerOutput::__construct
+ * @covers \MensBeam\Foundation\Catcher\PlainTextHandler::dispatchCallback
* @covers \MensBeam\Foundation\Catcher\PlainTextHandler::handleCallback
- * @covers \MensBeam\Foundation\Catcher\PlainTextHandler::serializeThrowable
+ * @covers \MensBeam\Foundation\Catcher\PlainTextHandler::dispatchCallback
* @covers \MensBeam\Foundation\Catcher\ThrowableController::__construct
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getErrorType
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getPrevious
@@ -71,7 +73,10 @@ class TestPlainTextHandler extends \PHPUnit\Framework\TestCase {
*/
public function testMethod_log(): void {
$l = Phony::mock(LoggerInterface::class);
- $h = new PlainTextHandler([ 'logger' => $l->get() ]);
+ $h = new PlainTextHandler([
+ 'logger' => $l->get(),
+ 'outputToStderr' => false
+ ]);
$e = [
'notice' => [
@@ -99,14 +104,25 @@ class TestPlainTextHandler extends \PHPUnit\Framework\TestCase {
foreach ($e as $k => $v) {
foreach ($v as $vv) {
$h->handle(new ThrowableController(new Error('Ook!', $vv)));
+
+ ob_start();
+ $h->dispatch();
+ ob_end_clean();
+
$l->$k->called();
}
}
$h->handle(new ThrowableController(new \PharException('Ook!')));
+ ob_start();
+ $h->dispatch();
+ ob_end_clean();
$l->alert->called();
$h->handle(new ThrowableController(new \RuntimeException('Ook!')));
+ ob_start();
+ $h->dispatch();
+ ob_end_clean();
$l->alert->called();
}
}
\ No newline at end of file
diff --git a/tests/cases/TestThrowableController.php b/tests/cases/TestThrowableController.php
index 9f7f126..7a6707f 100644
--- a/tests/cases/TestThrowableController.php
+++ b/tests/cases/TestThrowableController.php
@@ -32,14 +32,11 @@ class TestThrowableController extends \PHPUnit\Framework\TestCase {
* @covers \MensBeam\Foundation\Catcher\Error::__construct
* @covers \MensBeam\Foundation\Catcher\Handler::__construct
* @covers \MensBeam\Foundation\Catcher\Handler::dispatch
- * @covers \MensBeam\Foundation\Catcher\Handler::getControlCode
- * @covers \MensBeam\Foundation\Catcher\Handler::getOutputCode
* @covers \MensBeam\Foundation\Catcher\Handler::handle
* @covers \MensBeam\Foundation\Catcher\Handler::print
- * @covers \MensBeam\Foundation\Catcher\HandlerOutput::__construct
* @covers \MensBeam\Foundation\Catcher\PlainTextHandler::dispatchCallback
* @covers \MensBeam\Foundation\Catcher\PlainTextHandler::handleCallback
- * @covers \MensBeam\Foundation\Catcher\PlainTextHandler::serializeThrowable
+ * @covers \MensBeam\Foundation\Catcher\PlainTextHandler::serializeOutputThrowable
* @covers \MensBeam\Foundation\Catcher\ThrowableController::__construct
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getPrevious
* @covers \MensBeam\Foundation\Catcher\ThrowableController::getThrowable
@@ -63,6 +60,24 @@ class TestThrowableController extends \PHPUnit\Framework\TestCase {
$this->assertSame(\E_USER_WARNING, $c->getLastThrowable()->getCode());
$c->unregister();
+ $c = new Catcher(new PlainTextHandler([ 'outputToStderr' => false ]));
+ $c->preventExit = true;
+ $c->throwErrors = false;
+ ob_start();
+ trigger_error('Ook!', \E_USER_NOTICE);
+ ob_end_clean();
+ $this->assertSame(\E_USER_NOTICE, $c->getLastThrowable()->getCode());
+ $c->unregister();
+
+ $c = new Catcher(new PlainTextHandler([ 'outputToStderr' => false ]));
+ $c->preventExit = true;
+ $c->throwErrors = false;
+ ob_start();
+ trigger_error('Ook!', \E_USER_ERROR);
+ ob_end_clean();
+ $this->assertSame(\E_USER_ERROR, $c->getLastThrowable()->getCode());
+ $c->unregister();
+
// These others will be tested by invoking the method directly
$c = new ThrowableController(new Error('Ook!', \E_ERROR));
$this->assertSame('PHP Fatal Error', $c->getErrorType());
@@ -90,6 +105,9 @@ class TestThrowableController extends \PHPUnit\Framework\TestCase {
$this->assertNull($c->getErrorType());
$c = new ThrowableController(new \Exception('Ook!'));
$this->assertNull($c->getErrorType());
+
+ // For code coverage purposes.
+ $this->assertNull($c->getErrorType());
}
/**
@@ -122,7 +140,7 @@ class TestThrowableController extends \PHPUnit\Framework\TestCase {
$f = false;
try {
- throw new \Exception(message: 'Ook!', previous: new Error('Ook!', \E_ERROR));
+ throw new \Exception(message: 'Ook!', previous: new Error(message: 'Ook!', code: \E_ERROR, previous: new \Exception('Ook!')));
} catch (\Throwable $t) {
$c = new ThrowableController($t);
$f = $c->getFrames();
@@ -164,5 +182,10 @@ class TestThrowableController extends \PHPUnit\Framework\TestCase {
// For code coverage purposes; should use the cached value instead of calculating
// the frames over again.
$f = $c->getFrames();
+
+ // Lastly test for a RangeException
+ $this->expectException(\RangeException::class);
+ $c = new ThrowableController(new \Exception('Ook!'));
+ $c->getFrames(-1);
}
}
\ No newline at end of file
diff --git a/tests/phpunit.dist.xml b/tests/phpunit.dist.xml
index 2d675df..672fd0c 100644
--- a/tests/phpunit.dist.xml
+++ b/tests/phpunit.dist.xml
@@ -20,6 +20,7 @@
cases/TestCatcher.php
cases/TestHandler.php
cases/TestHTMLHandler.php
+ cases/TestJSONHandler.php
cases/TestPlainTextHandler.php
cases/TestThrowableController.php