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三個屬性信息前端
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 裏定義的三個屬性值sql
關閉日誌寫入的方法,在config.php裏,
1 'log' => [ 2 // 能夠關閉日誌寫入 3 'type' => 'test', 4 ],
日誌級別json
ThinkPHP對系統的日誌按照級別來分類,而且這個日誌級別徹底能夠本身定義,系統內部使用的級別包括:後端
系統提供了不一樣日誌級別的快速記錄方法,例如:
api
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思想,就至關於電影院裏的檢票員檢票的流程,只有一個檢票口,每一個來看電影的人統一在檢票口進行檢票,這樣就很節省資源了.
惟一的入口,統一的檢票處理.