Dustin Wilson
2 years ago
5 changed files with 242 additions and 8 deletions
@ -0,0 +1,216 @@ |
|||||
|
<?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 HTMLHandler extends ThrowableHandler { |
||||
|
public const CONTENT_TYPE = 'text/html'; |
||||
|
|
||||
|
/** The number of backtrace frames in which to print arguments; defaults to 5 */ |
||||
|
protected int $_backtraceArgFrameLimit = 5; |
||||
|
/** 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; 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 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 { |
||||
|
$document = new \DOMDocument(); |
||||
|
$frag = $document->createDocumentFragment(); |
||||
|
|
||||
|
if ($this->_outputTime && $this->_timeFormat !== '') { |
||||
|
$p = $document->createElement('p'); |
||||
|
$time = $document->createElement('time'); |
||||
|
$time->appendChild($document->createTextNode((new \DateTime())->format($this->_timeFormat))); |
||||
|
$p->appendChild($time); |
||||
|
$frag->appendChild($p); |
||||
|
} |
||||
|
|
||||
|
$frag->appendChild($this->buildThrowable($document, $throwable, $controller)); |
||||
|
if ($this->_outputPrevious) { |
||||
|
$prev = $throwable->getPrevious(); |
||||
|
$prevController = $controller->getPrevious(); |
||||
|
while ($prev) { |
||||
|
$p = $document->createElement('p'); |
||||
|
$small = $document->createElement('small'); |
||||
|
$small->appendChild($document->createTextNode('Caused by ↴')); |
||||
|
$p->appendChild($small); |
||||
|
$frag->appendChild($p); |
||||
|
$frag->appendChild($this->buildThrowable($document, $prev, $prevController)); |
||||
|
$prev = $prev->getPrevious(); |
||||
|
$prevController = $prevController->getPrevious(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if ($this->_outputBacktrace) { |
||||
|
$frames = $controller->getFrames(); |
||||
|
$p = $document->createElement('p'); |
||||
|
$p->appendChild($document->createTextNode('Stack trace:')); |
||||
|
$frag->appendChild($p); |
||||
|
|
||||
|
if (count($frames) > 0) { |
||||
|
$ol = $document->createElement('ol'); |
||||
|
$p->appendChild($ol); |
||||
|
$num = 1; |
||||
|
foreach ($frames as $frame) { |
||||
|
$li = $document->createElement('li'); |
||||
|
$args = (!empty($frame['args']) && $this->_backtraceArgFrameLimit >= $num); |
||||
|
$t = ($args) ? $document->createElement('p') : $li; |
||||
|
|
||||
|
if (!empty($frame['error'])) { |
||||
|
$b = $document->createElement('b'); |
||||
|
$b->appendChild($document->createTextNode($frame['error'])); |
||||
|
$t->appendChild($b); |
||||
|
$t->appendChild($document->createTextNode(' (')); |
||||
|
$code = $document->createElement('code'); |
||||
|
$code->appendChild($document->createTextNode($frame['class'])); |
||||
|
$t->appendChild($code); |
||||
|
$t->appendChild($document->createTextNode(')')); |
||||
|
} elseif (!empty($frame['class'])) { |
||||
|
$code = $document->createElement('code'); |
||||
|
$code->appendChild($document->createTextNode($frame['class'])); |
||||
|
$t->appendChild($code); |
||||
|
} |
||||
|
|
||||
|
$class = $frame['class'] ?? ''; |
||||
|
$function = $frame['function'] ?? ''; |
||||
|
if ($function) { |
||||
|
if ($class) { |
||||
|
$code->firstChild->textContent .= "::{$function}()"; |
||||
|
} else { |
||||
|
$code = $document->createElement('code'); |
||||
|
$code->appendChild($document->createTextNode("{$function}()")); |
||||
|
$t->appendChild($code); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
$t->appendChild($document->createTextNode(' ')); |
||||
|
$i = $document->createElement('i'); |
||||
|
$i->appendChild($document->createTextNode($frame['file'])); |
||||
|
$t->appendChild($i); |
||||
|
$t->appendChild($document->createTextNode(":{$frame['line']}")); |
||||
|
|
||||
|
if ($args) { |
||||
|
$li->appendChild($t); |
||||
|
$pre = $document->createElement('pre'); |
||||
|
$pre->appendChild($document->createTextNode(var_export($frame['args'], true))); |
||||
|
$li->appendChild($pre); |
||||
|
} |
||||
|
|
||||
|
$ol->appendChild($li); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
$this->_result = $frag; |
||||
|
|
||||
|
if (\PHP_SAPI !== 'cli' && $this->_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)); |
||||
|
|
||||
|
$this->sendContentTypeHeader(); |
||||
|
http_response_code(500); |
||||
|
echo $document->saveHTML(); |
||||
|
return (!$this->_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 { |
||||
|
$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; |
||||
|
} |
||||
|
} |
Loading…
Reference in new issue