异常实战
目的如下:
-
1、将系统抛出的非严重错误屏蔽,并做warning警报处理。而且不做中断。
-
2、将系统抛出的严重错误屏蔽,并提示内部错误和做error警报处理,程序中断。
-
3、将手动抛出的异常提示用户,中断程序。
ThinkPHP5.1.19 错误处理分析
1、public/index.php // 入口
2、require DIR . '/../thinkphp/base.php' 19line
// 注册错误和异常处理机制
Error::register();//19行
3、Error::register()的内容,处理异常都是中断程序,然后写日志。thinkphp/library/think/Error.php 32line
error_reporting(E_ALL);
set_error_handler([__CLASS__, 'appError']);
set_exception_handler([__CLASS__, 'appException']);
register_shutdown_function([__CLASS__, 'appShutdown']);
/**
* Exception Handler
* @access public
* @param \Exception|\Throwable $e
*/
public static function appException($e)
{
if (!$e instanceof \Exception) {
$e = new ThrowableError($e);
}
self::getExceptionHandler()->report($e); // 这一调用了self::getExceptionHandler(),//**这里提供给用户设置处理异常的途径。**
if (PHP_SAPI == 'cli') {
self::getExceptionHandler()->renderForConsole(new ConsoleOutput, $e);
} else {
self::getExceptionHandler()->render($e)->send();
}
}
4、public/index.php 将注册在容器的thinkphp/library/think/App.php执行run
$app = Container::get('app')->run()->send();
5、thinkphp/library/think/App.php 375line的run()
// 初始化应用
$this->initialize(); //379
6、$this->initialize(); thinkphp/library/think/App.php 232line 设置异常处理类。
// 注册异常处理类
if ($this->config('app.exception_handle')) {
Error::setExceptionHandler($this->config('app.exception_handle'));//设置用户的处理异常类
}
1、目的一,将系统抛出的非严重错误屏蔽,并做warning警报处理。而且不做中断。
原先的异常处理是会中断的,不中断异常那么就要替换原先的错误处理,重写set-error-handler,要生效是放最后写,替换之前写的。而且要放在响应给客户端之前。
根据上面分析,要放在thinkphp/library/think/App.php的$this->initialize();后。
public function run()
{
try {
// 初始化应用
$this->initialize();
// 监听app_init
$this->hook->listen('app_init'); // ****这里放在这里****
if ($this->bindModule) {
// 模块/控制器绑定
$this->route->bind($this->bindModule);
} elseif ($this->config('app.auto_bind_module')) {
// 入口自动绑定
$name = pathinfo($this->request->baseFile(), PATHINFO_FILENAME);
if ($name && 'index' != $name && is_dir($this->appPath . $name)) {
$this->route->bind($name);
}
}
// 监听app_dispatch
$this->hook->listen('app_dispatch');
$dispatch = $this->dispatch;
if (empty($dispatch)) {
// 路由检测
$dispatch = $this->routeCheck()->init();
}
// 记录当前调度信息
$this->request->dispatch($dispatch);
// 记录路由和请求信息
if ($this->appDebug) {
$this->log('[ ROUTE ] ' . var_export($this->request->routeInfo(), true));
$this->log('[ HEADER ] ' . var_export($this->request->header(), true));
$this->log('[ PARAM ] ' . var_export($this->request->param(), true));
}
// 监听app_begin
$this->hook->listen('app_begin');
// 请求缓存检查
$this->checkRequestCache(
$this->config('request_cache'),
$this->config('request_cache_expire'),
$this->config('request_cache_except')
);
$data = null;
} catch (HttpResponseException $exception) {
$dispatch = null;
$data = $exception->getResponse();
}
$this->middleware->add(function (Request $request, $next) use ($dispatch, $data) {
return is_null($data) ? $dispatch->run() : $data;
});
$response = $this->middleware->dispatch($this->request);
// 监听app_end
$this->hook->listen('app_end', $response);
return $response;
}
这里提供能很多可以自定义的地方,我放在$this->hook->listen('app_init'); 这里,也就是 application/tags.php的app_init
// 应用初始化
'app_init' => [
'app\common\init\ExceptionInit'
],
application\common\init\ExceptionInit.php的内容。
<?php
namespace app\common\init;
use think\Request;
class ExceptionInit
{
/**
* @param Request $request
*/
public function __construct(Request $request)
{
$this->request = $request;
}
public function run()
{
if (!\think\facade\Env::get('APP_DEBUG')) {
// set to the user defined error handler
set_error_handler(error_handler(function ($log){
trace($log, 'warning');
}));//error_handler方法是自定义的处理方法,通过回调的方式处理异常结果,这里记录日志为warning类型。
}
}
}
error_handler方法内容
if (! function_exists('error_handler')) {
function error_handler(\Closure $logCallback) {
return function ($errno, $errstr, $errfile, $errline) use($logCallback){
if (!(error_reporting() & $errno)) {
// This error code is not included in error_reporting, so let it fall
// through to the standard PHP error handler
return false;
}
// $errstr may need to be escaped:
$errstr = htmlspecialchars($errstr);
switch ($errno) {
case E_DEPRECATED:
$errLevel = "E_DEPRECATED:";
break;
case E_NOTICE:
$errLevel = "E_NOTICE:";
break;
case E_WARNING:
$errLevel = "E_WARNING:";
break;
default:
$errLevel = "Unknown error type:";
break;
}
if(is_callable($logCallback)){
$logCallback($errLevel . '[' . $errno . ']' . "{$errstr} {$errfile} {$errline}");
}
/* Don't execute PHP internal error handler */
return true;
};
}
}
2、目标二、三。将系统抛出的严重错误屏蔽,并提示内部错误和做error警报处理,程序中断。将手动抛出的异常提示用户,中断程序。
严重错误和自定义抛出会走set_exception_handler。前面因为要不中断所以重写了set_error_handler,然后中断的继续用系统的处理方式,系统提供了异常处理的方式,config/app.php 的138line。
// 异常处理handle类 留空使用 \think\exception\Handle 交给模块下的config
'exception_handle' => 'app\common\exception\ExceptionHandler'
application\common\exception\ExceptionHandler.php文件
<?php
namespace app\common\exception;
use app\common\service\DingdingService;
use OneHour\Core\Controller\AjaxResponseTrait;
use think\exception\Handle;
use think\exception\ThrowableError;
class ExceptionHandler extends Handle
{
use AjaxResponseTrait;
public function report(\Exception $exception)
{
if (!env('APP_DEBUG')) {
//严重错误处理
if ($exception instanceof ThrowableError) {
$msg = $exception->getMessage() .
$exception->getFile() .
$exception->getLine() . ' ' .
$exception->getCode();
DingdingService::getInstance()->errorSend($msg);//钉钉异常通知
trace($msg, 'error');//日志记录
$this->failResponse('服务器内部错误,请稍后再试')->send();//统一内部错误
}
//常规错误
$this->failResponse($exception->getMessage())->send();
}
}
}