123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293 |
- <?php
- /**
- * This file is part of the ForkBB <https://github.com/forkbb>.
- *
- * @copyright (c) Visman <mio.visman@yandex.ru, https://github.com/MioVisman>
- * @license The MIT License (MIT)
- */
- declare(strict_types=1);
- namespace ForkBB\Core;
- use Throwable;
- class ErrorHandler
- {
- /**
- * Уровень буфера вывода на котором работает обработчик
- * @var int
- */
- protected $obLevel;
- /**
- * Описание ошибки
- * @var array
- */
- protected $error;
- /**
- * Флаг отправки сообщения в лог
- * @var bool
- */
- protected $logged = false;
- /**
- * Скрываемая часть пути до файла
- * @var string
- */
- protected $hidePath;
- /**
- * Список ошибок
- * @var array
- */
- protected $type = [
- 0 => 'OTHER_ERROR',
- \E_ERROR => 'E_ERROR',
- \E_WARNING => 'E_WARNING',
- \E_PARSE => 'E_PARSE',
- \E_NOTICE => 'E_NOTICE',
- \E_CORE_ERROR => 'E_CORE_ERROR',
- \E_CORE_WARNING => 'E_CORE_WARNING',
- \E_COMPILE_ERROR => 'E_COMPILE_ERROR',
- \E_COMPILE_WARNING => 'E_COMPILE_WARNING',
- \E_USER_ERROR => 'E_USER_ERROR',
- \E_USER_WARNING => 'E_USER_WARNING',
- \E_USER_NOTICE => 'E_USER_NOTICE',
- \E_STRICT => 'E_STRICT',
- \E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR',
- \E_DEPRECATED => 'E_DEPRECATED',
- \E_USER_DEPRECATED => 'E_USER_DEPRECATED',
- ];
- public function __construct()
- {
- $this->hidePath = \realpath(__DIR__ . '/../../');
- \set_error_handler([$this, 'errorHandler']);
- \set_exception_handler([$this, 'exceptionHandler']);
- \register_shutdown_function([$this, 'shutdownHandler']);
- \ob_start();
- $this->obLevel = \ob_get_level();
- }
- public function __destruct()
- {
- \restore_error_handler();
- \restore_exception_handler();
- //????
- }
- /**
- * Обрабатыет перехватываемые ошибки
- */
- public function errorHandler(int $type, string $message, string $file, string $line): bool
- {
- $error = [
- 'type' => $type,
- 'message' => $message,
- 'file' => $file,
- 'line' => $line,
- 'trace' => \debug_backtrace(0),
- ];
- $this->log($error);
- if ($type & \error_reporting()) {
- $this->error = $error;
- exit(1);
- }
- $this->logged = false;
- return true;
- }
- /**
- * Обрабатывает не перехваченные исключения
- */
- public function exceptionHandler(Throwable $e): void
- {
- $this->error = [
- 'type' => 0, //????
- 'message' => $e->getMessage(),
- 'file' => $e->getFile(),
- 'line' => $e->getLine(),
- 'trace' => $e->getTrace(),
- ];
- }
- /**
- * Окончательно обрабатывает ошибки (в том числе фатальные) и исключения
- */
- public function shutdownHandler(): void
- {
- if (isset($this->error['type'])) {
- $show = true;
- } else {
- $show = false;
- $this->error = \error_get_last();
- if (isset($this->error['type'])) {
- switch ($this->error['type']) {
- case \E_ERROR:
- case \E_PARSE:
- case \E_CORE_ERROR:
- case \E_CORE_WARNING:
- case \E_COMPILE_ERROR:
- case \E_COMPILE_WARNING:
- $show = true;
- break;
- }
- }
- }
- if (
- isset($this->error['type'])
- && ! $this->logged
- ) {
- $this->log($this->error);
- }
- while (\ob_get_level() > $this->obLevel) {
- \ob_end_clean();
- }
- if (\ob_get_level() === $this->obLevel) {
- if ($show) {
- \ob_end_clean();
- $this->show($this->error);
- } else {
- \ob_end_flush();
- }
- }
- }
- /**
- * Отправляет сообщение в лог
- */
- protected function log(array $error): void
- {
- $this->logged = true;
- $message = \preg_replace('%[\x00-\x1F]%', ' ', $this->message($error));
- \error_log($message);
- }
- /**
- * Выводит сообщение об ошибке
- *
- * @param array $error
- */
- protected function show(array $error): void
- {
- \header('HTTP/1.1 500 Internal Server Error');
- \header('Content-Type: text/html; charset=utf-8');
- echo <<<'EOT'
- <!DOCTYPE html>
- <html lang="en" dir="ltr">
- <head>
- <meta charset="utf-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>500 Internal Server Error</title>
- </head>
- <body>
- EOT;
- if (1 == \ini_get('display_errors')) {
- echo '<p>' . $this->e($this->message($error)) . '</p>';
- if (
- isset($error['trace'])
- && \is_array($error['trace'])
- ) {
- echo '<div><p>Trace:</p><ol>';
- foreach ($error['trace'] as $cur) {
- if (
- isset($cur['file'], $cur['line'], $error['file'], $error['line'])
- && $error['line'] === $cur['line']
- && $error['file'] === $cur['file']
- ) {
- continue;
- }
- $line = $cur['file'] ?? '-';
- $line .= '(' . ($cur['line'] ?? '-') . '): ';
- if (isset($cur['class'])) {
- $line .= $cur['class'] . $cur['type'];
- }
- $line .= ($cur['function'] ?? 'unknown') . '(';
- if (
- ! empty($cur['args'])
- && \is_array($cur['args'])
- ) {
- $comma = '';
- foreach($cur['args'] as $arg) {
- $type = \gettype($arg);
- switch ($type) {
- case 'boolean':
- $type = $arg ? 'true' : 'false';
- break;
- case 'array':
- $type .= '(' . \count($arg) . ')';
- break;
- case 'resource':
- $type = \get_resource_type($arg);
- break;
- case 'object':
- $type .= '{' . \get_class($arg) . '}';
- break;
- }
- $line .= $comma . $type;
- $comma = ', ';
- }
- }
- $line .= ')';
- $line = $this->e(\str_replace($this->hidePath, '...', $line));
- echo "<li>{$line}</li>";
- }
- echo '</ol></div>';
- }
- } else {
- echo '<p>Oops</p>';
- }
- echo <<<'EOT'
- </body>
- </html>
- EOT;
- }
- /**
- * Формирует сообщение
- */
- protected function message(array $error): string
- {
- $type = $this->type[$error['type']] ?? $this->type[0];
- $file = \str_replace($this->hidePath, '...', $error['file']);
- return "PHP {$type}: \"{$error['message']}\" in {$file}:[{$error['line']}]";
- }
- /**
- * Экранирует спецсимволов HTML-сущностями
- */
- protected function e(string $arg): string
- {
- return \htmlspecialchars($arg, \ENT_HTML5 | \ENT_QUOTES | \ENT_SUBSTITUTE, 'UTF-8');
- }
- }
|