6-5 实现自定义全局异常处理 下 16:04php
1 <?php 2 /** 3 * Created by PhpStorm. 4 * User: Haima 5 * Date: 2018/7/8 6 * Time: 15:58 7 */ 8 9 namespace app\api\controller\v1; 10 use app\api\model\Banner as BannerModel; 11 use app\api\validate\IDMustBePostiveInt; 12 use app\lib\exception\BannerMissException; 13 14 class Banner 15 { 16 /** 17 * 获取指定id的banner信息 18 * @url /banner/:id 19 * @http GET 20 * @id banner的id号 21 */ 22 public function getBanner($id) 23 { 24 25 (new IDMustBePostiveInt())->goCheck(); //验证$id是否为正整数 26 $banner = BannerModel::getBannerById($id);//调用model 27 if (!$banner){ 28 throw new BannerMissException(); //判断结果不存在,抛出异常 29 } 30 // return $banner; 31 } 32 }
1 <?php 2 /** 3 * Created by PhpStorm. 4 * User: Hiama 5 * Date: 2018/7/12 6 * Time: 1:16 7 */ 8 9 namespace app\api\model; 10 class Banner 11 { 12 public static function getBannerById($id){ 13 return null; 14 } 15 }
因此ExceptionHandler最终就会抛出BannerMissException里定义的code,msg和errorCode三个属性信息html
1 <?php 2 /** 3 * Created by PhpStorm. 4 * User: Haima 5 * Date: 2018/7/12 6 * Time: 3:11 7 */ 8 9 namespace app\lib\exception; 10 use Exception; 11 use think\exception\Handle; 12 use think\Request; 13 14 class ExceptionHandler extends Handle 15 { 16 private $code; 17 private $msg; 18 private $errorCode; 19 20 public function render(Exception $e) 21 { 22 if($e instanceof BaseException){ 23 $this->code = $e->code; 24 $this->msg = $e->msg; 25 $this->errorCode = $e->errorCode; 26 } 27 else{ 28 $this->code = 500; 29 $this->msg = '服务器错误,不想给你看'; 30 $this->errorCode = 999; 31 } 32 $request = Request::instance(); 33 $result = array( 34 'msg' => $this->msg, 35 'error_code' => $this->errorCode, 36 'request_url' => $request->url() 37 ); 38 return json($result,$this->code); 39 } 40 }
<?php /** * Created by Haima. * Author:Haima * QQ:228654416 * Date: 2018/7/12 * Time: 20:48 */ namespace app\lib\exception; class BannerMissException extends BaseException { public $code = 404; public $msg = '请求的Banner不存在'; public $errrCode = 40000; }
1 <?php 2 /** 3 * Created by Haima. 4 * Author:Haima 5 * QQ:228654416 6 * Date: 2018/7/12 7 * Time: 20:49 8 */ 9 10 namespace app\lib\exception; 11 12 13 use think\Exception; 14 15 class BaseException extends Exception 16 { 17 //HTTP 状态码 404,200 18 public $code = 400; 19 20 //错误的具体信息 21 public $msg = '参数错误'; 22 23 //自定义的错误码 24 public $errorCode = 10000; 25 }
返回BannerMissException 里定义的三个属性值redis
关闭日志写入的方法,在config.php里,
1 'log' => [ 2 // 能够关闭日志写入 3 'type' => 'test', 4 ],
日志级别编程
ThinkPHP对系统的日志按照级别来分类,而且这个日志级别彻底能够本身定义,系统内部使用的级别包括:json
系统提供了不一样日志级别的快速记录方法,例如:
后端
1 Log::error('错误信息'); 2 Log::info('日志信息'); 3 // 和下面的用法等效 4 Log::record('错误信息','error'); 5 Log::record('日志信息','info');
还封装了一个助手函数用于日志记录,例如:
1 trace('错误信息','error'); 2 trace('日志信息','info');
config.php里关闭了系统自动生成日志
修改thinkphp生成日志的位置
在public/index.php里
define('LOG_PATH', __DIR__ . '/../log/'); //修改thinkphp生成日志的位置
在Banner.php控制器里临时抛出服务器异常作测试
ExceptionHandler里封装服务器异常recordErrorLog() 生成日志的函数
ExceptionHandler里判断抛出的异常是服务器异常,走else里的代码,并调用封装的recordErrorLog() 生成日志的函数,写日志
由于config.php里关闭了系统自动写日志,因此在recordErrorLog() 函数里要初始化一下日志
Log::record() 记录日志信息到内存 上面须要引入 use think\Log;
Log::record('测试日志信息,这是警告级别','notice');
1 <?php 2 /** 3 * Created by PhpStorm. 4 * User: Haima 5 * Date: 2018/7/12 6 * Time: 3:11 7 */ 8 9 namespace app\lib\exception; 10 use Exception; 11 use think\exception\Handle; 12 use think\Log; 13 use think\Request; 14 15 class ExceptionHandler extends Handle 16 { 17 private $code; 18 private $msg; 19 private $errorCode; 20 21 public function render(Exception $e) 22 { 23 if($e instanceof BaseException){ 24 $this->code = $e->code; 25 $this->msg = $e->msg; 26 $this->errorCode = $e->errorCode; 27 } 28 else{ 29 $this->code = 500; 30 $this->msg = '服务器错误,不想给你看'; 31 $this->errorCode = 999; 32 $this->recordErrorLog($e); //调用服务器异常错误 33 } 34 $request = Request::instance(); 35 $result = array( 36 'msg' => $this->msg, 37 'error_code' => $this->errorCode, 38 'request_url' => $request->url() 39 ); 40 return json($result,$this->code); 41 } 42 43 //服务器异常错误 44 private function recordErrorLog(Exception $e){ 45 Log::init([ 46 'type'=>'File', //生成的类型是文件 47 'path'=>LOG_PATH, //日志生成的路径 ,这里LOG_PATH的路径已经在public/index.php里定义了 48 'level'=>['error'] // 日志记录级别,使用数组表示 49 ]); 50 Log::record($e->getMessage(),'error'); //写入日志 51 } 52 }
给控制器发送请求:
此时已经在项目根目录里自动成功日志目录了
配置文件里的内容只能读取它里的某些配置信息,不要用它来作数据保存,
若是要保存数据能够写入数据库,redis缓存,thinkphp自提缓存,或者其它缓存的地方,或者保存到全局变量里
前端人员显示报错的页面,后端人员显示报错的json信息
思路:
在ExceptionHandler.php里的服务器异常里作判断,
判断配置里debug是否为true
(把它作为一个开关,一般上线后会关闭debug调适,这样服务器异常就会开启,异常就会写入日志里,固然你也能够自定义一个开关),
若是debug为ture就显示tp5框架自身的报错页面,给前台开发人员看,
不然就显示json的报错信息并写入日志中,给后端的开发人员看.
ExceptionHandler.php里读取debug值作为写日志信息的开关判断
1 <?php 2 /** 3 * Created by PhpStorm. 4 * User: Haima 5 * Date: 2018/7/12 6 * Time: 3:11 7 */ 8 9 namespace app\lib\exception; 10 use Exception; 11 use think\exception\Handle; 12 use think\Log; 13 use think\Request; 14 15 class ExceptionHandler extends Handle 16 { 17 private $code; 18 private $msg; 19 private $errorCode; 20 21 public function render(Exception $e) 22 { 23 if($e instanceof BaseException){ 24 //若是是自定义异常,则控制http状态码,不须要记录日志 25 //由于这些一般是由于客户端传递参数错误或者是用户请求形成的异常 26 //不该当记录日志 27 $this->code = $e->code; 28 $this->msg = $e->msg; 29 $this->errorCode = $e->errorCode; 30 } 31 else{ 32 // 若是是服务器未处理的异常,将http状态码设置为500,并记录日志 33 //Config::get('app_debug'); //获取config.php里的配置信息 34 if (config('app_debug')){ //获取config.php里的配置信息若是是true走这里 35 // 若是是前台调适人员看就显示json格式错误 36 // 调试状态下须要显示TP默认的异常页面,由于TP的默认就是页面 37 // 很容易看出问题 38 return parent::render($e); //调用thinkphp5默认的报错页面 39 } 40 //若是是后台调适人员看就显示json格式错误 41 $this->code = 500; 42 $this->msg = '服务器错误,不想给你看'; 43 $this->errorCode = 999; 44 $this->recordErrorLog($e); //调用服务器异常错误 45 } 46 $request = Request::instance(); 47 $result = array( 48 'msg' => $this->msg, 49 'error_code' => $this->errorCode, 50 'request_url' => $request->url() 51 ); 52 return json($result,$this->code); 53 } 54 55 //服务器异常错误 56 private function recordErrorLog(Exception $e){ 57 Log::init([ 58 'type'=>'File', //生成的类型是文件 59 'path'=>LOG_PATH, //日志生成的路径 60 'level'=>['error'] // 日志记录级别,使用数组表示 61 ]); 62 Log::record($e->getMessage(),'error'); //写入日志 63 } 64 }
'app_debug' => false,
访问:
已经写入日志中:
为true时:显示报错页面,并不会写入自定义的log目录的日志中
'app_debug' => true,
自定义的异常就显示咱们自定义的json形式的报错就能够了,没有必要再显示页面的报错了(没有意义),因此就不用再作if判断了,
思路:
全部的整个参数验证层的错误处理都集中在BaseValidate这里,它承担了全部验证层的验证工做.是惟的验证入口
当客户访问z.com/banner/0.1时,banner.php/getBanner调用BaseValidate里的goCheck()方法验证,
goCheck()方法经过$this->check($params)调用IDMustBePostiveInt验证传入的$id是否是正整数
(IDMustBePostiveInt已经继承BaseValidate,BaseValidate 又继承 Validate,因此能够用$this->check($params)直接调用到),
当传入的参数不正确,BaseValidate验证不经过,抛出异常(这里须要它返回一个json结构体的消息,还要指明错误的缘由,而不该该被反回服务器错误信息给隐藏掉),
ExceptionHandler捕捉到异常,并判断是异常类型后,抛出相应的异常信息.
优点:BaseValidate只管抛出异常,异常的类型交由ExceptionHandler来判断,而后决定抛出什么样的异常.
1 <?php 2 /** 3 * Created by PhpStorm. 4 * User: Haima 5 * Date: 2018/7/8 6 * Time: 15:58 7 */ 8 9 namespace app\api\controller\v1; 10 use app\api\model\Banner as BannerModel; 11 use app\api\validate\IDMustBePostiveInt; 12 use app\lib\exception\BannerMissException; 13 use think\Exception; 14 15 class Banner 16 { 17 /** 18 * 获取指定id的banner信息 19 * @url /banner/:id 20 * @http GET 21 * @id banner的id号 22 */ 23 public function getBanner($id) 24 { 25 26 (new IDMustBePostiveInt())->goCheck(); //验证$id是否为正整数 27 $banner = BannerModel::getBannerById($id);//调用model 28 if (!$banner){ 29 // throw new BannerMissException(); //判断结果不存在,抛出异常 30 throw new Exception('服务器内部异常'); //临时抛出服务器异常测试用 31 } 32 // return $banner; 33 } 34 }
1 <?php 2 /** 3 * Created by PhpStorm. 4 * User: Administrator 5 * Date: 2018/7/10 6 * Time: 21:49 7 */ 8 9 namespace app\api\validate; 10 11 use app\lib\exception\parameterException; 12 use think\Exception; 13 use think\Request; 14 use think\Validate; 15 16 class BaseValidate extends Validate 17 { 18 19 //全部的整个参数验证层的错误处理都集中在这里,承担了全部验证层的验证工做 20 public function goCheck() 21 { 22 //获取http传入的参数 23 //方法一: 24 // $request = Request::instance(); 25 // $params = $request->param(); 26 // 方法二: 27 $params = Request::instance()->param(); 28 //对参数进行校验 29 if(!$this->check($params)){ 30 31 // 这里须要它返回一个json结构体的消息,还要指明错误的缘由, 32 // 而不该该被反回服务器错误信息给隐藏掉 33 $e = new parameterException(); 34 //用验证器里定义的错误信息重写parameterException下的$msg属性, 35 //这样就能正常报出验证器里定义的异常信息了 36 $e->msg=$this->error; 37 // 抛出的必需是一个Exception异常, 38 // 由于parameterException必需继承自BaseException,而BaseException已经继承了Exception. 39 // 因此parameterException天然也就继承了Exception 40 throw $e; //优点,只管抛出异常,异常的类型交由ExceptionHandler来判断,而后决定抛出什么样的异常 41 42 /* $error = $this->error; 43 throw new Exception($error);*/ 44 } 45 else{ 46 return true; 47 } 48 } 49 }
验证必需为正整数
1 <?php 2 /** 3 * Created by PhpStorm. 4 * User: Administrator 5 * Date: 2018/7/10 6 * Time: 12:18 7 */ 8 9 namespace app\api\validate; 10 11 class IDMustBePostiveInt extends BaseValidate 12 { 13 protected $rule = [ 14 'id' => 'require|isPositiveInteger' 15 ]; 16 17 protected function isPositiveInteger($value,$rule='', $data='', $field='') 18 { 19 if(is_numeric($value) && is_int($value + 0) && ($value + 0) >0) 20 { 21 return true; 22 } 23 else 24 { 25 return $field.'必需是正整'; 26 } 27 } 28 }
1 <?php 2 /** 3 * Created by Haima. 4 * Author:Haima 5 * QQ:228654416 6 * Date: 2018/7/15 7 * Time: 10:48 8 */ 9 10 namespace app\lib\exception; 11 12 13 class parameterException extends BaseException 14 { 15 public $code = 400; 16 //随便定义一个通用的提示信息,以后validate抛出异常信息时,会被重写成validate里定义的错误信息 17 public $msg = '参数错误'; 18 //经过参数错误,凡是被验证层检查出来的错误都返回一个10000 19 public $errorCode = 10000; 20 }
ExceptionHandler里捕捉到$e异常后,判断是属于BaseExcetion参数错误异常,
捕捉到的BaseValidate里抛出的parameterException里的异常
全部就把parameterException的异常信息给继承过来,BaseValidate又改写了$msg属性,BaseValidate里的$this->error取的又是IDMustBePostiveInt里定义的错误信息
最终于会抛出验证器IDMustBePostiveInt里定义的错误信息
1 <?php 2 /** 3 * Created by PhpStorm. 4 * User: Haima 5 * Date: 2018/7/12 6 * Time: 3:11 7 */ 8 9 namespace app\lib\exception; 10 use Exception; 11 use think\exception\Handle; 12 use think\Log; 13 use think\Request; 14 15 class ExceptionHandler extends Handle 16 { 17 private $code; 18 private $msg; 19 private $errorCode; 20 21 public function render(Exception $e) 22 { 23 if($e instanceof BaseException){ 24 //若是是自定义异常,则控制http状态码,不须要记录日志 25 //由于这些一般是由于客户端传递参数错误或者是用户请求形成的异常 26 //不该当记录日志 27 //这里取的是当前抛出异常的控制器里定义的异常信息 28 $this->code = $e->code; 29 $this->msg = $e->msg; 30 $this->errorCode = $e->errorCode; 31 } 32 else{ 33 // 若是是服务器未处理的异常,将http状态码设置为500,并记录日志 34 //Config::get('app_debug'); //获取config.php里的配置信息 35 if (config('app_debug')){ //获取config.php里的配置信息若是是true走这里 36 // 若是是前台调适人员看就显示json格式错误 37 // 调试状态下须要显示TP默认的异常页面,由于TP的默认就是页面 38 // 很容易看出问题 39 return parent::render($e); //调用thinkphp5默认的报错页面 40 } 41 //若是是后台调适人员看就显示json格式错误 42 $this->code = 500; 43 $this->msg = '服务器错误,不想给你看'; 44 $this->errorCode = 999; 45 $this->recordErrorLog($e); //调用服务器异常错误 46 } 47 $request = Request::instance(); 48 $result = array( 49 'msg' => $this->msg, 50 'error_code' => $this->errorCode, 51 'request_url' => $request->url() 52 ); 53 return json($result,$this->code); 54 } 55 56 //服务器异常错误 57 private function recordErrorLog(Exception $e){ 58 Log::init([ 59 'type'=>'File', //生成的类型是文件 60 'path'=>LOG_PATH, //日志生成的路径 61 'level'=>['error'] // 日志记录级别,使用数组表示 62 ]); 63 Log::record($e->getMessage(),'error'); //写入日志 64 } 65 }
报出的就是validate里定义的错误信息
$code 和 $errorCode 就是 parameterException里定义的信息
异常处理优化代码:
parameterException继承了BaseException,因此它天然也继承了父类里的__construct构造函数,
BaseValidate里实例化parameterException,并经过传参给,parameterException里的__construct构造函数重写了parameterException里的$msg属性,
ExceptionHandler里判断异常后,抛出异常时取的是当前被抛出异常的 parameterException里的$msg属性信息.
1 <?php 2 /** 3 * Created by PhpStorm. 4 * User: Administrator 5 * Date: 2018/7/10 6 * Time: 21:49 7 */ 8 9 namespace app\api\validate; 10 11 use app\lib\exception\parameterException; 12 use think\Exception; 13 use think\Request; 14 use think\Validate; 15 16 class BaseValidate extends Validate 17 { 18 19 //全部的整个参数验证层的错误处理都集中在这里,承担了全部验证层的验证工做 20 public function goCheck() 21 { 22 //获取http传入的参数 23 //方法一: 24 // $request = Request::instance(); 25 // $params = $request->param(); 26 // 方法二: 27 $params = Request::instance()->param(); 28 //对参数进行校验 29 if(!$this->check($params)){ 30 31 //优化下面的代码,实例化时调用传入参数重写parameterException下的$msg属性 32 //而不用$e->msg=$this->error;这样的方式重写 33 $e = new parameterException([ 34 'msg'=>$this->error //只改写msg 其它两个值还用原始值 35 ]); 36 37 throw $e; //优点,只管抛出异常,异常的类型交由ExceptionHandler来判断,而后决定抛出什么样的异常 38 39 } 40 else{ 41 return true; 42 } 43 } 44 }
1 <?php 2 /** 3 * Created by Haima. 4 * Author:Haima 5 * QQ:228654416 6 * Date: 2018/7/12 7 * Time: 20:49 8 */ 9 10 namespace app\lib\exception; 11 12 use think\Exception; 13 14 class BaseException extends Exception 15 { 16 //HTTP 状态码 404,200 17 public $code = 400; 18 19 //错误的具体信息 20 public $msg = '参数错误'; 21 22 //自定义的错误码 23 public $errorCode = 10000; 24 25 public function __construct($param = []){ 26 if (!is_array($param)){ 27 //下面两种处理方式均可以,看我的怎么理解了. 28 //方法一:若是传来的不是数组就返回回去,认为不是要改写属性 29 return; 30 //方法二: 若是不是数组就报错,强制让传入数组 31 // throw new Exception('参数必需是数组'); 32 } 33 if(array_key_exists('code',$param)){ 34 $this->code = $param['code']; 35 } 36 37 if(array_key_exists('msg',$param)){ 38 $this->msg = $param['msg']; 39 } 40 41 if(array_key_exists('errorCode',$param)){ 42 $this->errorCode = $param['error']; 43 } 44 } 45 }
1 <?php 2 /** 3 * Created by Haima. 4 * Author:Haima 5 * QQ:228654416 6 * Date: 2018/7/15 7 * Time: 10:48 8 */ 9 10 namespace app\lib\exception; 11 12 13 use think\Exception; 14 15 class parameterException extends BaseException 16 { 17 public $code = 400; 18 //随便定义一个通用的提示信息,以后validate抛出异常信息时,会被重写成validate里定义的错误信息 19 public $msg = '参数错误'; 20 //经过参数错误,凡是被验证层检查出来的错误都返回一个10000 21 public $errorCode = 10000; 22 23 24 }
IDMustBePosttiveInt里再加入num的验证
z.com/banner/0.1?num=4
不要把代码写的太直白,要站在一个更高的角度,用比较抽象的方式,统一的整体的来处理处某一个问题,
举个例子:
AOP思想,就至关于电影院里的检票员检票的流程,只有一个检票口,每一个来看电影的人统一在检票口进行检票,这样就很节省资源了.
惟一的入口,统一的检票处理.