Codeigniter4学习之漂亮的异常页面 浏览次数:292 日期:2020-07-04 13:33:00 ### 起因 CodeIgniter一直是我喜欢的一个框架,因为他轻量小巧,可是随着环境的变化,这个框架没有及时的更新,似乎有些没有赶上时代,但因为他小巧轻量的特点,仍然有无数的开发者在使用。 据说Codeigniter4出来了,引入命名空间,整个框架都脱胎换骨,这让我很兴奋,于是根据官方文档,上去就写了个hello world程序,发现框架提供了漂亮的异常页面,exception抛出来可以理解,fatal error和parse error的语法错误都可以提示出来,哎呀,像发现了新大陆一样兴奋,带着喜欢和好奇感赶紧研究了一番,研究之后就想据为己有,于是就想把这个异常抛出的操作提取出来,一方面是为了研究学习,一方面是想更灵活一些,在任何项目中都可以使用 ### 提取完整代码 JsonExceptionHandler.php文件代码 ```php initialize(); } /** * Responsible for registering the error, exception and shutdown * handling of our application. */ public function initialize() { error_reporting(E_ALL); //Set the Exception Handler set_exception_handler([$this, 'exceptionHandler']); // Set the Error Handler set_error_handler([$this, 'errorHandler']); // Set the handler for shutdown to catch Parse errors // Do we need this in PHP7? register_shutdown_function([$this, 'shutdownHandler']); } //-------------------------------------------------------------------- /** * Catches any uncaught errors and exceptions, including most Fatal errors * (Yay PHP7!). Will log the error, display it if display_errors is on, * and fire an event that allows custom actions to be taken at this point. * * @param \Throwable $exception */ public function exceptionHandler(Throwable $exception) { // @codeCoverageIgnoreStart $codes = $this->determineCodes($exception); $statusCode = $codes[0]; $exitCode = $codes[1]; $this->render($exception, $statusCode); exit($exitCode); } //-------------------------------------------------------------------- /** * Even in PHP7, some errors make it through to the errorHandler, so * convert these to Exceptions and let the exception handler log it and * display it. * * This seems to be primarily when a user triggers it with trigger_error(). * * @param integer $severity * @param string $message * @param string|null $file * @param integer|null $line * * @throws \ErrorException */ public function errorHandler(int $severity, string $message, string $file = null, int $line = null) { if (! (error_reporting() & $severity)) { return; } // Convert it to an exception and pass it along. throw new ErrorException($message, 0, $severity, $file, $line); } //-------------------------------------------------------------------- /** * Checks to see if any errors have happened during shutdown that * need to be caught and handle them. */ public function shutdownHandler() { $error = error_get_last(); // If we've got an error that hasn't been displayed, then convert // it to an Exception and use the Exception handler to display it // to the user. if (! is_null($error)) { // Fatal Error? if (in_array($error['type'], [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE])) { $this->exceptionHandler(new ErrorException($error['message'], $error['type'], 0, $error['file'], $error['line'])); } } } //-------------------------------------------------------------------- /** * Gathers the variables that will be made available to the view. * * @param \Throwable $exception * @param integer $statusCode * * @return array */ protected function collectVars(Throwable $exception, int $statusCode): array { return [ 'title' => get_class($exception), 'type' => get_class($exception), 'code' => $statusCode, 'message' => $exception->getMessage() ?? '(null)', 'file' => $exception->getFile(), 'line' => $exception->getLine(), 'trace' => $exception->getTrace(), ]; } /** * Determines the HTTP status code and the exit status code for this request. * * @param \Throwable $exception * * @return array */ protected function determineCodes(Throwable $exception): array { $statusCode = abs($exception->getCode()); if ($statusCode < 100 || $statusCode > 599) { $exitStatus = $statusCode + self::EXIT__AUTO_MIN; // 9 is EXIT__AUTO_MIN if ($exitStatus > self::EXIT__AUTO_MAX) // 125 is EXIT__AUTO_MAX { $exitStatus = self::EXIT_ERROR; // EXIT_ERROR } $statusCode = 500; } else { $exitStatus = 1; // EXIT_ERROR } return [ $statusCode ?? 500, $exitStatus, ]; } //-------------------------------------------------------------------- //-------------------------------------------------------------------- // Display Methods //-------------------------------------------------------------------- /** * Clean Path * * This makes nicer looking paths for the error output. * * @param string $file * * @return string */ public static function cleanPath(string $file): string { return $file; } //-------------------------------------------------------------------- /** * Creates a syntax-highlighted version of a PHP file. * * @param string $file * @param integer $lineNumber * @param integer $lines * * @return boolean|string */ public static function highlightFile(string $file, int $lineNumber, int $lines = 15) { if (empty($file) || ! is_readable($file)) { return false; } // Set our highlight colors: if (function_exists('ini_set')) { ini_set('highlight.comment', '#767a7e; font-style: italic'); ini_set('highlight.default', '#c7c7c7'); ini_set('highlight.html', '#06B'); ini_set('highlight.keyword', '#f1ce61;'); ini_set('highlight.string', '#869d6a'); } try { $source = file_get_contents($file); } catch (Throwable $e) { return false; } $source = str_replace(["\r\n", "\r"], "\n", $source); $source = explode("\n", highlight_string($source, true)); $source = str_replace('', "\n", $source[1]); $source = explode("\n", str_replace("\r\n", "\n", $source)); // Get just the part to show $start = $lineNumber - (int) round($lines / 2); $start = $start < 0 ? 0 : $start; // Get just the lines we need to display, while keeping line numbers... $source = array_splice($source, $start, $lines, true); // Used to format the line number in the source $format = '% ' . strlen(sprintf('%s', $start + $lines)) . 'd'; $out = ''; // Because the highlighting may have an uneven number // of open and close span tags on one line, we need // to ensure we can close them all to get the lines // showing correctly. $spans = 1; foreach ($source as $n => $row) { $spans += substr_count($row, ']+>#', $row, $tags); $out .= sprintf("{$format} %s\n%s", $n + $start + 1, strip_tags($row), implode('', $tags[0]) ); } else { $out .= sprintf('' . $format . ' %s', $n + $start + 1, $row) . "\n"; } } if ($spans > 0) { $out .= str_repeat('', $spans); } return '' . $out . ''; } } trait RenderTrait { /** * Given an exception and status code will display the error to the client. * * @param \Throwable $exception * @param integer $statusCode */ protected function render(Throwable $exception, int $statusCode) { // Prepare the vars $vars = $this->collectVars($exception, $statusCode); extract($vars); ob_start(); $error_id = uniqid('error', true); echo ''; echo ''; echo ''; echo ''; echo ''; echo ''.htmlspecialchars($title, ENT_SUBSTITUTE, 'UTF-8').''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''.htmlspecialchars($title, ENT_SUBSTITUTE, 'UTF-8'). ($exception->getCode() ? ' #' . $exception->getCode() : '').''; echo ''.$exception->getMessage().''; echo ''; echo ''; echo ''; echo ''.static::cleanPath($file, $line).' at line '. $line.''; if (is_file($file)) { echo ''; echo static::highlightFile($file, $line, 15); echo ''; } echo ''; echo ''; echo ''; echo 'Backtrace'; echo ''; echo ''; echo ''; echo ''; foreach ($trace as $index => $row) { echo ''; echo ''; //Trace info if (isset($row['file']) && is_file($row['file'])){ if (isset($row['function']) && in_array($row['function'], ['include', 'include_once', 'require', 'require_once'])) { echo $row['function'] .' ' . static::cleanPath($row['file']); } else { echo static::cleanPath($row['file']).' : '.$row['line']; } }else { echo '{PHP internal code}'; } //Class/Method if (isset($row['class'])) { echo ' — '.$row['class'] . $row['type'] . $row['function']; if (! empty($row['args'])) { $args_id = $error_id . 'args' . $index; echo '( arguments )'; echo ''; echo ''; $params = null; // Reflection by name is not available for closure function if (substr( $row['function'], -1 ) !== '}') { $mirror = isset( $row['class'] ) ? new \ReflectionMethod( $row['class'], $row['function'] ) : new \ReflectionFunction( $row['function'] ); $params = $mirror->getParameters(); } foreach ($row['args'] as $key => $value) { echo ''; echo ''. htmlspecialchars(isset($params[$key]) ? '$' . $params[$key]->name : "#$key", ENT_SUBSTITUTE, 'UTF-8').''; echo ''. print_r($value, true) .''; echo ''; } echo ''; echo ''; } else{ echo '()'; } } if (! isset($row['class']) && isset($row['function'])) { echo ' — '.$row['function'].'()'; } echo ''; if (isset($row['file']) && is_file($row['file']) && isset($row['class'])) { echo ''; echo static::highlightFile($row['file'], $row['line']); echo ''; } echo ''; } echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; $buffer = ob_get_contents(); ob_end_clean(); echo $buffer; } } ``` ### 使用方法 提取出来之后,只需要一个文件,只需要三步,就可以使用了 1. 引入文件 2. 使用命名空间下的类 3. 注册 ```php 最后更新时间为: 8个月前 (2020-07-04 13:33:00)