From aaa0fd7c9f02f2cd11f5d5340577483590578fe4 Mon Sep 17 00:00:00 2001 From: yunwuxin <448901948@qq.com> Date: Fri, 13 May 2016 19:05:41 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E6=A1=86=E6=9E=B6=E7=9A=84?= =?UTF-8?q?=E5=BC=82=E5=B8=B8=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- convention.php | 4 +- library/think/App.php | 60 ++--- library/think/Error.php | 194 ++++----------- library/think/Exception.php | 17 +- library/think/Log.php | 20 +- library/think/Response.php | 2 +- .../think/exception/DbBindParamException.php | 9 + library/think/exception/Handle.php | 222 ++++++++++++++++++ library/think/exception/HttpException.php | 37 +++ ...xception.php => HttpResponseException.php} | 34 ++- library/think/exception/ThrowableError.php | 49 ++++ 11 files changed, 434 insertions(+), 214 deletions(-) create mode 100644 library/think/exception/Handle.php create mode 100644 library/think/exception/HttpException.php rename library/think/exception/{NotFoundException.php => HttpResponseException.php} (52%) create mode 100644 library/think/exception/ThrowableError.php diff --git a/convention.php b/convention.php index 86f205c1..10fa3218 100644 --- a/convention.php +++ b/convention.php @@ -123,9 +123,7 @@ return [ // 异常页面的模板文件 'exception_tmpl' => THINK_PATH . 'tpl' . DS . 'think_exception.tpl', - // 异常处理忽略的错误类型,支持PHP所有的错误级别常量,多个级别可以用|运算法 - // 参考:http://php.net/manual/en/errorfunc.constants.php - 'exception_ignore_type' => 0, + // 错误显示信息,非调试模式有效 'error_message' => '页面错误!请稍后再试~', // 错误定向页面 diff --git a/library/think/App.php b/library/think/App.php index 49bcd33f..e37d3f2b 100644 --- a/library/think/App.php +++ b/library/think/App.php @@ -11,6 +11,7 @@ namespace think; +use think\exception\HttpResponseException; use think\Response; /** @@ -24,7 +25,8 @@ class App * 执行应用程序 * @access public * @param \think\Request $request Request对象 - * @return void + * @return \think\Response + * @throws Exception */ public static function run($request) { @@ -76,32 +78,36 @@ class App APP_DEBUG && Log::record('[ ROUTE ] ' . var_export($dispatch, true), 'info'); // 监听app_begin APP_HOOK && Hook::listen('app_begin', $dispatch); - switch ($dispatch['type']) { - case 'redirect': - // 执行重定向跳转 - header('Location: ' . $dispatch['url'], true, $dispatch['status']); - break; - case 'module': - // 模块/控制器/操作 - $data = self::module($dispatch['module'], $config); - break; - case 'controller': - // 执行控制器操作 - $data = Loader::action($dispatch['controller'], $dispatch['params']); - break; - case 'method': - // 执行回调方法 - $data = self::invokeMethod($dispatch['method'], $dispatch['params']); - break; - case 'function': - // 规则闭包 - $data = self::invokeFunction($dispatch['function'], $dispatch['params']); - break; - case 'finish': - // 已经完成 不再继续执行 - break; - default: - throw new Exception('dispatch type not support', 10008); + try { + switch ($dispatch['type']) { + case 'redirect': + // 执行重定向跳转 + header('Location: ' . $dispatch['url'], true, $dispatch['status']); + break; + case 'module': + // 模块/控制器/操作 + $data = self::module($dispatch['module'], $config); + break; + case 'controller': + // 执行控制器操作 + $data = Loader::action($dispatch['controller'], $dispatch['params']); + break; + case 'method': + // 执行回调方法 + $data = self::invokeMethod($dispatch['method'], $dispatch['params']); + break; + case 'function': + // 规则闭包 + $data = self::invokeFunction($dispatch['function'], $dispatch['params']); + break; + case 'finish': + // 已经完成 不再继续执行 + break; + default: + throw new Exception('dispatch type not support', 10008); + } + } catch (HttpResponseException $exception){ + $data = $exception->getResponse(); } // 输出数据到客户端 if (isset($data)) { diff --git a/library/think/Error.php b/library/think/Error.php index 9b760e13..26fa20c9 100644 --- a/library/think/Error.php +++ b/library/think/Error.php @@ -11,9 +11,9 @@ namespace think; -use think\Config; use think\exception\ErrorException; -use think\Log; +use think\exception\Handle; +use think\exception\ThrowableError; class Error { @@ -23,66 +23,29 @@ class Error */ public static function register() { + error_reporting(-1); set_error_handler([__CLASS__, 'appError']); set_exception_handler([__CLASS__, 'appException']); register_shutdown_function([__CLASS__, 'appShutdown']); + + if (!APP_DEBUG) { + ini_set('display_errors', 'Off'); + } } /** * Exception Handler - * @param \Exception $exception - * @return bool true-禁止往下传播已处理过的异常 + * @param \Exception $e */ - public static function appException($exception) + public static function appException($e) { - // 收集异常数据 - if (APP_DEBUG) { - // 调试模式,获取详细的错误信息 - $data = [ - 'name' => get_class($exception), - 'file' => $exception->getFile(), - 'line' => $exception->getLine(), - 'message' => $exception->getMessage(), - 'trace' => $exception->getTrace(), - 'code' => self::getCode($exception), - 'source' => self::getSourceCode($exception), - 'datas' => self::getExtendData($exception), - - 'tables' => [ - 'GET Data' => $_GET, - 'POST Data' => $_POST, - 'Files' => $_FILES, - 'Cookies' => $_COOKIE, - 'Session' => isset($_SESSION) ? $_SESSION : [], - 'Server/Request Data' => $_SERVER, - 'Environment Variables' => $_ENV, - 'ThinkPHP Constants' => self::getTPConst(), - ], - ]; - $log = "[{$data['code']}]{$data['message']}[{$data['file']}:{$data['line']}]"; - } else { - // 部署模式仅显示 Code 和 Message - $data = [ - 'code' => $exception->getCode(), - 'message' => $exception->getMessage(), - ]; - $log = "[{$data['code']}]{$data['message']}"; + if (!$e instanceof \Exception) { + $e = new ThrowableError($e); } + + self::getExceptionHandler()->report($e); - // 记录异常日志 - Log::record($log, 'error'); - - /* 非API模式下的部署模式,跳转到指定的 Error Page */ - $error_page = Config::get('error_page'); - if (!(APP_DEBUG || IS_API) && !empty($error_page)) { - header("Location: {$error_page}"); - } else { - // 输出错误信息 - self::output($exception, $data); - } - - // 禁止往下传播已处理过的异常 - return true; + self::getExceptionHandler()->render($e)->send(); } /** @@ -91,31 +54,24 @@ class Error * @param integer $errstr 详细错误信息 * @param string $errfile 出错的文件 * @param integer $errline 出错行号 - * @return bool true-禁止往下传播已处理过的异常 + * @param array $errcontext + * @return bool true-禁止往下传播已处理过的异常 + * @throws ErrorException */ - public static function appError($errno, $errstr, $errfile = null, $errline = 0, array $errcontext = []) + public static function appError($errno, $errstr, $errfile = '', $errline = 0, $errcontext = []) { - if ($errno & Config::get('exception_ignore_type')) { - // 忽略的异常记录到日志 - Log::record("[{$errno}]{$errstr}[{$errfile}:{$errline}]", 'notice'); - } else { + if (error_reporting() & $errno) { // 将错误信息托管至 think\exception\ErrorException throw new ErrorException($errno, $errstr, $errfile, $errline, $errcontext); - // 禁止往下传播已处理过的异常 - return true; } } /** * Shutdown Handler - * @return bool true-禁止往下传播已处理过的异常; false-未处理的异常继续传播 */ public static function appShutdown() { - // 写入日志 - Log::save(); - - if ($error = error_get_last()) { + if (!is_null($error = error_get_last()) && self::isFatal($error['type'])) { // 将错误信息托管至think\ErrorException $exception = new ErrorException( $error['type'], @@ -124,108 +80,46 @@ class Error $error['line'] ); - /** - * Shutdown handler 中的异常将不被往下传播 - * 所以,这里我们必须手动传播而不能像 Error handler 中那样 throw - */ self::appException($exception); - // 禁止往下传播已处理过的异常 - return true; } - return false; + + // 写入日志 + Log::save(); } /** - * 输出异常信息 - * @param \Exception $exception - * @param array $data 异常信息 - * @return void + * 确定错误类型是否致命 + * + * @param int $type + * @return bool */ - public static function output($exception, array $data) + protected static function isFatal($type) { - http_response_code($exception instanceof Exception ? $exception->getHttpStatus() : 500); - - $type = Config::get('default_return_type'); - if (!APP_DEBUG && !Config::get('show_error_msg')) { - // 不显示详细错误信息 - $data['message'] = Config::get('error_message'); - } - if (IS_API && 'html' != $type) { - // 异常信息输出监听 - APP_HOOK && Hook::listen('error_output', $data); - // 输出异常内容 - Response::instance()->send($data, $type, Config::get('response_return')); - } else { - //ob_end_clean(); - extract($data); - include Config::get('exception_tmpl'); - } + return in_array($type, [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE]); } - /** - * 获取错误编码 - * ErrorException则使用错误级别作为错误编码 - * @param \Exception $exception - * @return integer 错误编码 - */ - private static function getCode($exception) - { - $code = $exception->getCode(); - if (!$code && $exception instanceof ErrorException) { - $code = $exception->getSeverity(); - } - return $code; - } /** - * 获取出错文件内容 - * 获取错误的前9行和后9行 - * @param \Exception $exception - * @return array 错误文件内容 + * Get an instance of the exception handler. + * + * @return \think\exception\Handle */ - private static function getSourceCode($exception) + protected static function getExceptionHandler() { - // 读取前9行和后9行 - $line = $exception->getLine(); - $first = ($line - 9 > 0) ? $line - 9 : 1; + static $handle; - try { - $contents = file($exception->getFile()); - $source = [ - 'first' => $first, - 'source' => array_slice($contents, $first - 1, 19), - ]; - } catch (Exception $e) { - $source = []; - } - return $source; - } + if (!$handle) { - /** - * 获取异常扩展信息 - * 用于非调试模式html返回类型显示 - * @param \Exception $exception - * @return array 异常类定义的扩展数据 - */ - private static function getExtendData($exception) - { - $data = []; - if ($exception instanceof Exception) { - $data = $exception->getData(); + if ($class = Config::get('exception_handle')) { + if (class_exists($class) && is_subclass_of($class, "\\think\\exception\\Handle")) { + $handle = new $class; + } + } + if (!$handle) { + $handle = new Handle(); + } } - return $data; - } - /** - * 获取ThinkPHP常量列表 - * @return array 常量列表 - */ - private static function getTPConst() - { - $consts = ['THINK_VERSION', 'THINK_PATH', 'LIB_PATH', 'EXTEND_PATH', 'MODE_PATH', 'CORE_PATH', 'TRAIT_PATH', 'APP_PATH', 'RUNTIME_PATH', 'LOG_PATH', 'CACHE_PATH', 'TEMP_PATH', 'MODULE_PATH', 'VIEW_PATH', 'APP_NAMESPACE', 'COMMON_MODULE', 'APP_MULTI_MODULE', 'MODULE_ALIAS', 'MODULE_NAME', 'CONTROLLER_NAME', 'ACTION_NAME', 'MODEL_LAYER', 'VIEW_LAYER', 'CONTROLLER_LAYER', 'APP_DEBUG', 'APP_HOOK', 'ENV_PREFIX', 'IS_API', 'VENDOR_PATH', 'APP_AUTO_RUN', 'APP_MODE', 'REQUEST_METHOD', 'IS_CGI', 'IS_WIN', 'IS_API', 'IS_CLI', 'IS_GET', 'IS_POST', 'IS_PUT', 'IS_AJAX', 'IS_DELETE', 'NOW_TIME', 'LANG_SET', 'EXT', 'DS', '__INFO__', '__EXT__']; - foreach ($consts as $const) { - $data[$const] = defined($const) ? constant($const) : 'undefined'; - } - return $data; + return $handle; } } diff --git a/library/think/Exception.php b/library/think/Exception.php index abf32018..bc4cbdb2 100644 --- a/library/think/Exception.php +++ b/library/think/Exception.php @@ -17,11 +17,6 @@ namespace think; */ class Exception extends \Exception { - /** - * 系统异常后发送给客户端的HTTP Status - * @var integer - */ - protected $httpStatus = 500; /** * 保存异常页面显示的额外Debug数据 @@ -43,7 +38,7 @@ class Exception extends \Exception * key2 value2 * * @param string $label 数据分类,用于异常页面显示 - * @param Array $data 需要显示的数据,必须为关联数组 + * @param array $data 需要显示的数据,必须为关联数组 */ final protected function setData($label, array $data) { @@ -59,13 +54,5 @@ class Exception extends \Exception { return $this->data; } - - /** - * 获取要发送给客户端的HTTP Status - * @return integer HTTP Status - */ - final public function getHttpStatus() - { - return $this->httpStatus; - } + } diff --git a/library/think/Log.php b/library/think/Log.php index 7b41c6ee..a7cb4ec9 100644 --- a/library/think/Log.php +++ b/library/think/Log.php @@ -68,7 +68,7 @@ class Log /** * 记录调试信息 - * @param mixed $msg 调试信息 + * @param mixed $msg 调试信息 * @param string $type 信息类型 * @return void */ @@ -95,15 +95,25 @@ class Log */ public static function save() { - if (is_null(self::$driver)) { - self::init(Config::get('log')); + if (!empty(self::$log)) { + if (is_null(self::$driver)) { + self::init(Config::get('log')); + } + + $result = self::$driver->save(self::$log); + + if ($result) { + self::$log = []; + } + + return $result; } - return self::$driver->save(self::$log); + return true; } /** * 实时写入日志信息 并支持行为 - * @param mixed $msg 调试信息 + * @param mixed $msg 调试信息 * @param string $type 信息类型 * @return bool */ diff --git a/library/think/Response.php b/library/think/Response.php index 55e450ae..e0943711 100644 --- a/library/think/Response.php +++ b/library/think/Response.php @@ -50,7 +50,7 @@ class Response * 初始化 * @access public * @param string $type 输出类型 - * @return \think\Request + * @return \think\Response */ public static function instance($type = '') { diff --git a/library/think/exception/DbBindParamException.php b/library/think/exception/DbBindParamException.php index 21ddcd58..f39c39ac 100644 --- a/library/think/exception/DbBindParamException.php +++ b/library/think/exception/DbBindParamException.php @@ -18,6 +18,15 @@ use think\exception\DbException; */ class DbBindParamException extends DbException { + + /** + * DbBindParamException constructor. + * @param string $message + * @param array $config + * @param string $sql + * @param array $bind + * @param int $code + */ public function __construct($message, $config, $sql, $bind, $code = 10502) { $this->setData('Bind Param', $bind); diff --git a/library/think/exception/Handle.php b/library/think/exception/Handle.php new file mode 100644 index 00000000..890ef93e --- /dev/null +++ b/library/think/exception/Handle.php @@ -0,0 +1,222 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +use Exception; +use think\Config; +use think\Log; +use think\Response; + +class Handle +{ + + protected $ignoreReport = [ + '\\think\\exception\\HttpException' + ]; + + /** + * Report or log an exception. + * + * @param \Exception $exception + * @return void + */ + public function report(Exception $exception) + { + if (!$this->isIgnoreReport($exception)) { + // 收集异常数据 + if (APP_DEBUG) { + $data = [ + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + 'message' => $exception->getMessage(), + 'code' => $this->getCode($exception) + ]; + $log = "[{$data['code']}]{$data['message']}[{$data['file']}:{$data['line']}]"; + } else { + $data = [ + 'code' => $exception->getCode(), + 'message' => $exception->getMessage(), + ]; + $log = "[{$data['code']}]{$data['message']}"; + } + + Log::record($log, 'error'); + } + } + + protected function isIgnoreReport(Exception $exception) + { + foreach ($this->ignoreReport as $class) { + if ($exception instanceof $class) { + return true; + } + } + + return false; + } + + /** + * Render an exception into an HTTP response. + * + * @param \Exception $e + * @return Response + */ + public function render(Exception $e) + { + if ($e instanceof HttpException) { + return $this->renderHttpException($e); + } else { + return $this->convertExceptionToResponse($e); + } + } + + /** + * @param HttpException $e + * @return \think\Response + */ + protected function renderHttpException(HttpException $e) + { + $status = $e->getStatusCode(); + + //TODO 根据状态码自动输出错误页面 + + return $this->convertExceptionToResponse($e); + } + + /** + * @param Exception $exception + * @return Response + */ + protected function convertExceptionToResponse(Exception $exception) + { + // 收集异常数据 + if (APP_DEBUG) { + // 调试模式,获取详细的错误信息 + $data = [ + 'name' => get_class($exception), + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + 'message' => $exception->getMessage(), + 'trace' => $exception->getTrace(), + 'code' => $this->getCode($exception), + 'source' => $this->getSourceCode($exception), + 'datas' => $this->getExtendData($exception), + + 'tables' => [ + 'GET Data' => $_GET, + 'POST Data' => $_POST, + 'Files' => $_FILES, + 'Cookies' => $_COOKIE, + 'Session' => isset($_SESSION) ? $_SESSION : [], + 'Server/Request Data' => $_SERVER, + 'Environment Variables' => $_ENV, + 'ThinkPHP Constants' => $this->getConst(), + ] + ]; + } else { + // 部署模式仅显示 Code 和 Message + $data = [ + 'code' => $exception->getCode(), + 'message' => $exception->getMessage(), + ]; + } + + if (!APP_DEBUG && !Config::get('show_error_msg')) { + // 不显示详细错误信息 + $data['message'] = Config::get('error_message'); + } + ob_start(); + ob_implicit_flush(0); + extract($data); + include Config::get('exception_tmpl'); + // 获取并清空缓存 + $content = ob_get_clean(); + + $response = Response::instance()->data($content); + + if ($exception instanceof HttpException) { + $statusCode = $exception->getStatusCode(); + //TODO 设置headers 等待response完善 + } + + if (!isset($statusCode)) { + $statusCode = 500; + } + $response->code($statusCode); + + return $response; + } + + /** + * 获取错误编码 + * ErrorException则使用错误级别作为错误编码 + * @param \Exception $exception + * @return integer 错误编码 + */ + protected function getCode(Exception $exception) + { + $code = $exception->getCode(); + if (!$code && $exception instanceof ErrorException) { + $code = $exception->getSeverity(); + } + return $code; + } + + /** + * 获取出错文件内容 + * 获取错误的前9行和后9行 + * @param \Exception $exception + * @return array 错误文件内容 + */ + protected function getSourceCode(Exception $exception) + { + // 读取前9行和后9行 + $line = $exception->getLine(); + $first = ($line - 9 > 0) ? $line - 9 : 1; + + try { + $contents = file($exception->getFile()); + $source = [ + 'first' => $first, + 'source' => array_slice($contents, $first - 1, 19), + ]; + } catch (Exception $e) { + $source = []; + } + return $source; + } + + + /** + * 获取异常扩展信息 + * 用于非调试模式html返回类型显示 + * @param \Exception $exception + * @return array 异常类定义的扩展数据 + */ + protected function getExtendData(Exception $exception) + { + $data = []; + if ($exception instanceof \think\Exception) { + $data = $exception->getData(); + } + return $data; + } + + /** + * 获取常量列表 + * @return array 常量列表 + */ + private static function getConst() + { + return get_defined_constants(true)['user']; + } +} \ No newline at end of file diff --git a/library/think/exception/HttpException.php b/library/think/exception/HttpException.php new file mode 100644 index 00000000..99beb002 --- /dev/null +++ b/library/think/exception/HttpException.php @@ -0,0 +1,37 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + + +class HttpException extends \RuntimeException +{ + private $statusCode; + private $headers; + + public function __construct($statusCode, $message = null, \Exception $previous = null, array $headers = [], $code = 0) + { + $this->statusCode = $statusCode; + $this->headers = $headers; + + parent::__construct($message, $code, $previous); + } + + public function getStatusCode() + { + return $this->statusCode; + } + + public function getHeaders() + { + return $this->headers; + } +} \ No newline at end of file diff --git a/library/think/exception/NotFoundException.php b/library/think/exception/HttpResponseException.php similarity index 52% rename from library/think/exception/NotFoundException.php rename to library/think/exception/HttpResponseException.php index 06ae2e5d..249e3da3 100644 --- a/library/think/exception/NotFoundException.php +++ b/library/think/exception/HttpResponseException.php @@ -1,27 +1,35 @@ +// | Author: yunwuxin <448901948@qq.com> // +---------------------------------------------------------------------- namespace think\exception; -use think\Exception; -/** - * Database相关异常处理类 - */ -class NotFoundException extends Exception +use think\Response; + +class HttpResponseException extends \RuntimeException { /** - * 系统异常后发送给客户端的HTTP Status - * @var integer + * @var Response */ - protected $httpStatus = 404; - -} + protected $response; + + public function __construct(Response $response) + { + $this->response = $response; + } + + public function getResponse() + { + return $this->response; + } + + +} \ No newline at end of file diff --git a/library/think/exception/ThrowableError.php b/library/think/exception/ThrowableError.php new file mode 100644 index 00000000..8ba26ea8 --- /dev/null +++ b/library/think/exception/ThrowableError.php @@ -0,0 +1,49 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + + +class ThrowableError extends \ErrorException +{ + public function __construct(\Throwable $e) + { + + if ($e instanceof \ParseError) { + $message = 'Parse error: ' . $e->getMessage(); + $severity = E_PARSE; + } elseif ($e instanceof \TypeError) { + $message = 'Type error: ' . $e->getMessage(); + $severity = E_RECOVERABLE_ERROR; + } else { + $message = 'Fatal error: ' . $e->getMessage(); + $severity = E_ERROR; + } + + parent::__construct( + $message, + $e->getCode(), + $severity, + $e->getFile(), + $e->getLine() + ); + + + $this->setTrace($e->getTrace()); + } + + protected function setTrace($trace) + { + $traceReflector = new \ReflectionProperty('Exception', 'trace'); + $traceReflector->setAccessible(true); + $traceReflector->setValue($this, $trace); + } +} \ No newline at end of file