use http\Env as HTTP;
+/**
+ * Exception and error handler
+ */
class ExceptionHandler
{
- function __construct() {
+ /**
+ * @var \http\Env\Response
+ */
+ private $response;
+
+ /**
+ * Set up error/exception/shutdown handler
+ */
+ public function __construct(\http\Env\Response $r) {
+ $this->response = $r;
set_exception_handler($this);
set_error_handler($this);
+ register_shutdown_function($this);
}
-
- function __invoke($e, $msg = null) {
- if ($e instanceof \Exception) {
+
+ private static function cleanBuffers() {
+ while (ob_get_level()) {
+ if (!@ob_end_clean()) {
+ break;
+ }
+ }
+ }
+
+ /**
+ * The exception/error/shutdown handler callback
+ */
+ public function __invoke($e = null, $msg = null) {
+ if ($e instanceof \Exception || $e instanceof \Throwable) {
try {
- echo static::html($e);
+ self::cleanBuffers();
+ echo static::htmlException($e);
} catch (\Exception $ignore) {
- HTTP::sendStatusCode(500);
+ headers_sent() or HTTP::setResponseCode(500);
+ die("FATAL ERROR:\n$e\n$ignore");
}
- } else {
+ } elseif (isset($e, $msg)) {
throw new \Exception($msg, $e);
+ } elseif (($error = error_get_last())) {
+ switch ($error["type"]) {
+ case E_PARSE:
+ case E_ERROR:
+ case E_USER_ERROR:
+ case E_CORE_ERROR:
+ case E_COMPILE_ERROR:
+ self::cleanBuffers();
+ $message = sprintf("%s in %s at line %d",
+ $error["message"], $error["file"], $error["line"]);
+ echo static::htmlError("Application Error", $message, 500, "");
+ break;
+ }
}
- return true;
}
-
- static function html(\Exception $e, array $title_tag = ["h1"], array $message_tag = ["p"], array $trace_tag = ["pre", "style='font-size:smaller'"]) {
- if ($e instanceof \http\Controller\Exception) {
+
+ /**
+ * Format an exception as HTML and send appropriate exception info as HTTP headers
+ * @param Throwable $e
+ * @param array $title_tag
+ * @param array $message_tag
+ * @param array $trace_tag
+ * @return string
+ */
+ public static function htmlException(/*\Throwable*/ $e, array $title_tag = ["h1"], array $message_tag = ["p"], array $trace_tag = ["pre", "style='font-size:smaller;overflow-x:scroll'"]) {
+ if ($e instanceof Exception) {
$code = $e->getCode() ?: 500;
- foreach ($e->getHeaders() as $key => $val) {
- HTTP::sendResponseHeader($key, $val);
- }
} else {
$code = 500;
}
+
+ for ($html = ""; $e; $e = $e->getPrevious()) {
+ $html .= static::htmlError(HTTP::getResponseStatusForCode($code),
+ $e->getMessage(), $code, $e->getTraceAsString(),
+ $title_tag, $message_tag, $trace_tag);
+ }
+ return $html;
+ }
+
+ /**
+ * Format an error as HTML
+ * @param string $title
+ * @param string $message
+ * @param int $code
+ * @param string $trace
+ * @param array $title_tag
+ * @param array $message_tag
+ * @param array $trace_tag
+ * @return string
+ */
+ public static function htmlError($title, $message, $code, $trace = null, array $title_tag = ["h1"], array $message_tag = ["p"], array $trace_tag = ["pre", "style='font-size:smaller;overflow-x:scroll'"]) {
HTTP::setResponseCode($code);
- $name = HTTP::getResponseStatusForCode($code);
+
$html = sprintf("<%s>%s</%s>\n<%s>%s</%s>\n",
- implode(" ", $title_tag), $name, $title_tag[0],
- implode(" ", $message_tag), $e->getMessage(), $message_tag[0]);
+ implode(" ", $title_tag), $title, $title_tag[0],
+ implode(" ", $message_tag), $message, $message_tag[0]);
if ($trace_tag) {
- $html .= sprintf("<%s>%s</%s>\n",
- implode(" ", $trace_tag), $e->getTraceAsString(), $trace_tag[0]);
+ if (!isset($trace)) {
+ ob_start();
+ debug_print_backtrace();
+ $trace = ob_get_clean();
+ }
+ if (!empty($trace)) {
+ $html .= sprintf("<%s>%s</%s>\n",
+ implode(" ", $trace_tag), $trace, $trace_tag[0]);
+ }
}
+
return $html;
}
-}
\ No newline at end of file
+}