Thinkphp5的自定义异常处理及其底层实现
前言
在很多php的项目中,错误信息的输出返回都通过异常抛出的方式进行,特别是在后端校验过程中,或者后端处理过程中突然遇到需要中断程序执行的情况,相比返回['code' => 999, 'message' => 'error']
,抛出异常无疑更简单明了 众所周知,php里边自定义异常处理函数需要用到内置方法 set_exception_handler
,而在 Thinkphp5 中可以由开发者自定义异常处理类进行接管
正文
涉及到的配置参数及类
如果需要自定义异常处理,则需要在 app.php
中配置 exception_handle
参数 参数支持回调模式和自定义类,正如官方文档所说
1 2 3 4 5 6 7 8 9 10 11 12 13
| 'exception_handle' => function($e) { if ($e instanceof \think\exception\ValidateException) { return json($e->getError(), 422); }
if ($e instanceof \think\exception\HttpException && request()->isAjax()) { return response($e->getMessage(), $e->getStatusCode()); } },
'exception_handle' => '\\app\\common\\exception\\Http',
|
自定义类需要继承 think\exception\Handle
并实现 render(),支持 return new Response()
直接向前端响应数据 比如在我的项目中,在 app/common/exception
下定义了 BaseException extends \think\Exception
,以后不同模块的自定义异常类都会继承 BaseException
,在自定义异常处理类中判断 $e instanceof BaseException
即可进行个性化处理 app/common/exception/BaseException.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?php
namespace app\common\exception;
use think\Exception;
class BaseException extends Exception { }
|
在 app/common/exception/Http.php
中实现了控制器抛出的验证器异常和基于 BaseException
的异常,并统一格式输出和错误日志记录,在实际使用过程中就特别方便 app/common/exception/Http.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| <?php
namespace app\common\exception;
use Exception; use think\exception\ErrorException; use think\exception\Handle; use think\exception\ValidateException; use think\facade\Log;
class Http extends Handle { public function render(Exception $e) { if ($e instanceof ValidateException) { Log::info('Exception-Validate:' . $e->getMessage()); return json([ 'code' => 888, 'message' => $e->getMessage(), 'data' => [], ]); }
if ($e instanceof BaseException) { Log::error('Exception-Logic:' . $e->getMessage()); return json([ 'code' => $e->getCode() ?: 777, 'message' => $e->getMessage(), 'data' => [], ]); }
if ($e instanceof ErrorException) { Log::error('Exception-Error:' . $e->getMessage()); return json([ 'code' => $e->getCode() ?: 666, 'message' => $e->getMessage(), 'data' => [], ]); } Log::error('Unknown-Error:' . $e->getMessage()); return json([ 'code' => $e->getCode() ?: 555, 'message' => env('APP_DEBUG') ? $e->getMessage() : '系统错误', 'data' => [] ]);
} }
|
底层是如何实现的
首先,定位到 thinkphp/library/think/Error.php
错误处理类,在其 register()
中可看到对 php 的异常处理函数注册方法set_exception_handler
的调用
1 2 3 4 5 6 7 8 9 10 11 12
|
public static function register() { error_reporting(E_ALL); set_error_handler([__CLASS__, 'appError']); set_exception_handler([__CLASS__, 'appException']); register_shutdown_function([__CLASS__, 'appShutdown']); }
|
再顺着看 appException
方法,发现使用了 self::getExceptionHandler()
获取异常处理对象,对象是由变量 self::$exceptionHandler
决定的,默认 \\think\\exception\\Handle
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
public static function register() { error_reporting(E_ALL); set_error_handler([__CLASS__, 'appError']); set_exception_handler([__CLASS__, 'appException']); register_shutdown_function([__CLASS__, 'appShutdown']); }
public static function appException($e) { if (!$e instanceof \Exception) { $e = new ThrowableError($e); }
self::getExceptionHandler()->report($e);
if (PHP_SAPI == 'cli') { self::getExceptionHandler()->renderForConsole(new ConsoleOutput, $e); } else { self::getExceptionHandler()->render($e)->send(); } }
|
看到这里并没有看到 app.php
中的 exception.handler
如何发挥作用的,那么继续往下看,由于 self::$exceptionHandler
由 setExceptionHandler
决定,透过编辑器可以找到其调用方如下图所示: 在 thinkphp/library/think/App.php
的 initialize()
中,可看到如下所示代码
1 2 3 4
| if ($this->config('app.exception_handle')) { Error::setExceptionHandler($this->config('app.exception_handle')); }
|
initialize()
是在应用启动时(执行 run()
)执行的初始化函数, public/index.php
1 2 3 4 5 6 7 8 9 10 11
| <?php
namespace think;
require __DIR__ . '/../thinkphp/base.php';
Container::get('app')->run()->send();
|
App.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
public function run() { try { $this->initialize();
$this->hook->listen('app_init'); } }
|
以及在同文件的 containerConfigUpdate()
中也能看到类似代码
1 2 3 4 5 6
| $config = $this->config->get();
if ($config['app']['exception_handle']) { Error::setExceptionHandler($config['app']['exception_handle']); }
|
这个函数可以顺着 initialize()
-> init()
-> containerConfigUpdate
找到,作用是对不同模块中的配置进行一次更新
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public function init($model) { $module = $module ? $module . DIRECTORY_SEPARATOR : ''; $path = $this->appPath . $module;
if (is_file($path . 'init.php')) { include $path . 'init.php'; } elseif (is_file($this->runtimePath . $module . 'init.php')) { include $this->runtimePath . $module . 'init.php'; } else { } $this->setModulePath($path);
if ($module) { $this->containerConfigUpdate($module); } }
|
总结
php中使用 set_exception_handler
处理异常,而Thinkphp5中可在 app.php
中自定义 excpetion_handler
处理类的方式统一方便的处理异常