|
|
@ -6,81 +6,145 @@ |
|
|
|
*/ |
|
|
|
|
|
|
|
declare(strict_types=1); |
|
|
|
namespace Mensbeam\Framework\Catcher; |
|
|
|
namespace MensBeam\Framework\Catcher; |
|
|
|
|
|
|
|
|
|
|
|
class HTMLHandler extends Handler { |
|
|
|
public const CONTENT_TYPE = 'text/html'; |
|
|
|
|
|
|
|
/** The number of backtrace frames in which to print arguments; defaults to 5 */ |
|
|
|
protected static int $_backtraceArgFrameLimit = 5; |
|
|
|
/** If true the handler will output backtraces; defaults to false */ |
|
|
|
protected static bool $_outputBacktrace = false; |
|
|
|
/** If true the handler will output previous throwables; defaults to true */ |
|
|
|
protected static bool $_outputPrevious = true; |
|
|
|
protected ?\DOMDocument $_document = null; |
|
|
|
protected static array $bullshit = []; |
|
|
|
/** If true the handler will output times to the output; defaults to true */ |
|
|
|
protected static bool $_outputTime = true; |
|
|
|
protected bool $_outputTime = true; |
|
|
|
/** The PHP-standard date format which to use for times printed to output */ |
|
|
|
protected static string $_timeFormat = 'H:i:s'; |
|
|
|
protected string $_timeFormat = 'H:i:s'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static function handle(\Throwable $throwable, ThrowableController $controller): bool { |
|
|
|
$document = new \DOMDocument(); |
|
|
|
$frag = $document->createDocumentFragment(); |
|
|
|
public function __construct(array $options = []) { |
|
|
|
parent::__construct($options); |
|
|
|
|
|
|
|
if ($this->_document === null) { |
|
|
|
$this->_document = new \DOMDocument(); |
|
|
|
$this->_document->loadHTML(<<<HTML |
|
|
|
<!DOCTYPE html> |
|
|
|
<html> |
|
|
|
<head><title>HTTP {$this->_httpCode}</title></head> |
|
|
|
<body></body> |
|
|
|
</html> |
|
|
|
HTML); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected function buildThrowable(ThrowableController $controller): \DOMElement { |
|
|
|
$throwable = $controller->getThrowable(); |
|
|
|
$p = $this->_document->createElement('p'); |
|
|
|
|
|
|
|
$class = $throwable::class; |
|
|
|
if ($throwable instanceof \Error) { |
|
|
|
$type = $controller->getErrorType(); |
|
|
|
if ($type !== null) { |
|
|
|
$b = $this->_document->createElement('b'); |
|
|
|
$b->appendChild($this->_document->createTextNode($type)); |
|
|
|
$p->appendChild($b); |
|
|
|
$p->appendChild($this->_document->createTextNode(' (')); |
|
|
|
$code = $this->_document->createElement('code'); |
|
|
|
$code->appendChild($this->_document->createTextNode($throwable::class)); |
|
|
|
$p->appendChild($code); |
|
|
|
$p->appendChild($this->_document->createTextNode(')')); |
|
|
|
} else { |
|
|
|
$code = $this->_document->createElement('code'); |
|
|
|
$code->appendChild($this->_document->createTextNode($throwable::class)); |
|
|
|
$p->appendChild($code); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
$p->appendChild($this->_document->createTextNode(': ')); |
|
|
|
$i = $this->_document->createElement('i'); |
|
|
|
$i->appendChild($this->_document->createTextNode($throwable->getMessage())); |
|
|
|
$p->appendChild($i); |
|
|
|
$p->appendChild($this->_document->createTextNode(' in file ')); |
|
|
|
$code = $this->_document->createElement('code'); |
|
|
|
$code->appendChild($this->_document->createTextNode($throwable->getFile())); |
|
|
|
$p->appendChild($code); |
|
|
|
$p->appendChild($this->_document->createTextNode(' on line ' . $throwable->getLine())); |
|
|
|
return $p; |
|
|
|
} |
|
|
|
|
|
|
|
protected function dispatchCallback(): void { |
|
|
|
$body = $this->_document->getElementsByTagName('body')[0]; |
|
|
|
foreach ($this->outputBuffer as $o) { |
|
|
|
if ($o->outputCode & self::SILENT) { |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
$body->appendChild($o->output); |
|
|
|
} |
|
|
|
|
|
|
|
if (self::$_outputTime && self::$_timeFormat !== '') { |
|
|
|
$p = $document->createElement('p'); |
|
|
|
$time = $document->createElement('time'); |
|
|
|
$time->appendChild($document->createTextNode((new \DateTime())->format(self::$_timeFormat))); |
|
|
|
$output = $this->_document->saveHTML(); |
|
|
|
if (\PHP_SAPI === 'CLI') { |
|
|
|
fprintf(\STDERR, "$output\n"); |
|
|
|
} else { |
|
|
|
echo $output; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
protected function handleCallback(ThrowableController $controller): HandlerOutput { |
|
|
|
$frag = $this->_document->createDocumentFragment(); |
|
|
|
|
|
|
|
if ($this->_outputTime && $this->_timeFormat !== '') { |
|
|
|
$p = $this->_document->createElement('p'); |
|
|
|
$time = $this->_document->createElement('time'); |
|
|
|
$time->appendChild($this->_document->createTextNode((new \DateTime())->format($this->_timeFormat))); |
|
|
|
$p->appendChild($time); |
|
|
|
$frag->appendChild($p); |
|
|
|
} |
|
|
|
|
|
|
|
$frag->appendChild(self::$buildThrowable($document, $throwable, $controller)); |
|
|
|
if (self::$_outputPrevious) { |
|
|
|
$prev = $throwable->getPrevious(); |
|
|
|
$frag->appendChild($this->buildThrowable($controller)); |
|
|
|
if ($this->_outputPrevious) { |
|
|
|
$prevController = $controller->getPrevious(); |
|
|
|
while ($prev) { |
|
|
|
$p = $document->createElement('p'); |
|
|
|
$small = $document->createElement('small'); |
|
|
|
$small->appendChild($document->createTextNode('Caused by ↴')); |
|
|
|
while ($prevController) { |
|
|
|
$p = $this->_document->createElement('p'); |
|
|
|
$small = $this->_document->createElement('small'); |
|
|
|
$small->appendChild($this->_document->createTextNode('Caused by ↴')); |
|
|
|
$p->appendChild($small); |
|
|
|
$frag->appendChild($p); |
|
|
|
$frag->appendChild(self::$buildThrowable($document, $prev, $prevController)); |
|
|
|
$prev = $prev->getPrevious(); |
|
|
|
$frag->appendChild($this->buildThrowable($prevController)); |
|
|
|
$prevController = $prevController->getPrevious(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (self::$_outputBacktrace) { |
|
|
|
if ($this->_outputBacktrace) { |
|
|
|
$frames = $controller->getFrames(); |
|
|
|
$p = $document->createElement('p'); |
|
|
|
$p->appendChild($document->createTextNode('Stack trace:')); |
|
|
|
$p = $this->_document->createElement('p'); |
|
|
|
$p->appendChild($this->_document->createTextNode('Stack trace:')); |
|
|
|
$frag->appendChild($p); |
|
|
|
|
|
|
|
if (count($frames) > 0) { |
|
|
|
$ol = $document->createElement('ol'); |
|
|
|
$ol = $this->_document->createElement('ol'); |
|
|
|
$p->appendChild($ol); |
|
|
|
$num = 1; |
|
|
|
foreach ($frames as $frame) { |
|
|
|
$li = $document->createElement('li'); |
|
|
|
$args = (!empty($frame['args']) && self::$_backtraceArgFrameLimit >= $num); |
|
|
|
$t = ($args) ? $document->createElement('p') : $li; |
|
|
|
$li = $this->_document->createElement('li'); |
|
|
|
$args = (!empty($frame['args']) && $this->_backtraceArgFrameLimit >= $num); |
|
|
|
$t = ($args) ? $this->_document->createElement('p') : $li; |
|
|
|
|
|
|
|
if (!empty($frame['error'])) { |
|
|
|
$b = $document->createElement('b'); |
|
|
|
$b->appendChild($document->createTextNode($frame['error'])); |
|
|
|
$b = $this->_document->createElement('b'); |
|
|
|
$b->appendChild($this->_document->createTextNode($frame['error'])); |
|
|
|
$t->appendChild($b); |
|
|
|
$t->appendChild($document->createTextNode(' (')); |
|
|
|
$code = $document->createElement('code'); |
|
|
|
$code->appendChild($document->createTextNode($frame['class'])); |
|
|
|
$t->appendChild($this->_document->createTextNode(' (')); |
|
|
|
$code = $this->_document->createElement('code'); |
|
|
|
$code->appendChild($this->_document->createTextNode($frame['class'])); |
|
|
|
$t->appendChild($code); |
|
|
|
$t->appendChild($document->createTextNode(')')); |
|
|
|
$t->appendChild($this->_document->createTextNode(')')); |
|
|
|
} elseif (!empty($frame['class'])) { |
|
|
|
$code = $document->createElement('code'); |
|
|
|
$code->appendChild($document->createTextNode($frame['class'])); |
|
|
|
$code = $this->_document->createElement('code'); |
|
|
|
$code->appendChild($this->_document->createTextNode($frame['class'])); |
|
|
|
$t->appendChild($code); |
|
|
|
} |
|
|
|
|
|
|
@ -90,22 +154,22 @@ class HTMLHandler extends Handler { |
|
|
|
if ($class) { |
|
|
|
$code->firstChild->textContent .= "::{$function}()"; |
|
|
|
} else { |
|
|
|
$code = $document->createElement('code'); |
|
|
|
$code->appendChild($document->createTextNode("{$function}()")); |
|
|
|
$code = $this->_document->createElement('code'); |
|
|
|
$code->appendChild($this->_document->createTextNode("{$function}()")); |
|
|
|
$t->appendChild($code); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
$t->appendChild($document->createTextNode(' ')); |
|
|
|
$i = $document->createElement('i'); |
|
|
|
$i->appendChild($document->createTextNode($frame['file'])); |
|
|
|
$t->appendChild($this->_document->createTextNode(' ')); |
|
|
|
$i = $this->_document->createElement('i'); |
|
|
|
$i->appendChild($this->_document->createTextNode($frame['file'])); |
|
|
|
$t->appendChild($i); |
|
|
|
$t->appendChild($document->createTextNode(":{$frame['line']}")); |
|
|
|
$t->appendChild($this->_document->createTextNode(":{$frame['line']}")); |
|
|
|
|
|
|
|
if ($args) { |
|
|
|
$li->appendChild($t); |
|
|
|
$pre = $document->createElement('pre'); |
|
|
|
$pre->appendChild($document->createTextNode(var_export($frame['args'], true))); |
|
|
|
$pre = $this->_document->createElement('pre'); |
|
|
|
$pre->appendChild($this->_document->createTextNode(var_export($frame['args'], true))); |
|
|
|
$li->appendChild($pre); |
|
|
|
} |
|
|
|
|
|
|
@ -114,57 +178,6 @@ class HTMLHandler extends Handler { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
$this->result = $frag; |
|
|
|
|
|
|
|
if (\PHP_SAPI !== 'cli' && self::$_output) { |
|
|
|
$document->loadHTML(sprintf( |
|
|
|
'<!doctype html><html><head><title>%s%s</title></head><body></body></html>', |
|
|
|
(isset($_SERVER['protocol'])) ? "{$_SERVER['protocol']} " : '', |
|
|
|
'500 Internal Server Error' |
|
|
|
)); |
|
|
|
$document->getElementsByTagName('body')[0]->appendChild($document->importNode($frag, true)); |
|
|
|
|
|
|
|
self::$sendContentTypeHeader(); |
|
|
|
http_response_code(500); |
|
|
|
echo $document->saveHTML(); |
|
|
|
return (!self::$_passthrough); |
|
|
|
} |
|
|
|
|
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected static function buildThrowable(\DOMDocument $document, \Throwable $throwable, ThrowableController $controller): \DOMElement { |
|
|
|
$p = $document->createElement('p'); |
|
|
|
|
|
|
|
$class = $throwable::class; |
|
|
|
if ($throwable instanceof \Error) { |
|
|
|
$type = $controller->getErrorType(); |
|
|
|
if ($type !== null) { |
|
|
|
$b = $document->createElement('b'); |
|
|
|
$b->appendChild($document->createTextNode($type)); |
|
|
|
$p->appendChild($b); |
|
|
|
$p->appendChild($document->createTextNode(' (')); |
|
|
|
$code = $document->createElement('code'); |
|
|
|
$code->appendChild($document->createTextNode($throwable::class)); |
|
|
|
$p->appendChild($code); |
|
|
|
$p->appendChild($document->createTextNode(')')); |
|
|
|
} else { |
|
|
|
$code = $document->createElement('code'); |
|
|
|
$code->appendChild($document->createTextNode($throwable::class)); |
|
|
|
$p->appendChild($code); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
$p->appendChild($document->createTextNode(': ')); |
|
|
|
$i = $document->createElement('i'); |
|
|
|
$i->appendChild($document->createTextNode($throwable->getMessage())); |
|
|
|
$p->appendChild($i); |
|
|
|
$p->appendChild($document->createTextNode(' in file ')); |
|
|
|
$code = $document->createElement('code'); |
|
|
|
$code->appendChild($document->createTextNode($throwable->getFile())); |
|
|
|
$p->appendChild($code); |
|
|
|
$p->appendChild($document->createTextNode(' on line ' . $throwable->getLine())); |
|
|
|
return $p; |
|
|
|
return new HandlerOutput($this->getControlCode(), $this->getOutputCode(), $frag); |
|
|
|
} |
|
|
|
} |