帶你拋出優雅的處理系統異常

請輸入圖片描述

關於拋出異常

如在個人上一篇文中所說的同樣, 在接口的設計中, 接口的返回的數據是很是重要的, 例如沒法避免的500等等, 這些都是要命的錯誤php

同時還有一個極大的問題, 就是在新增模塊中, 例如我最近須要新增一個 elasticsearh 的分詞查詢模塊, 這個在添加索引刪除索引等等操做的時候, 是很是容易致使拋出錯誤異常的.html

按照日常的解決思路, 咱們可能首先就是針對異常處理直接進行使用Exception進行捕獲,/偷笑, 我在之前的時候也是很是喜歡這麼作的 laravel

1) try catch (1)git

代碼案例:github

public function addIndex($data)
{
    $params = [
        'index' => 'show',
        'type' => 'store',
        'body' => $data
    ];

    try{
        $this->client->index($params);
    }catch (\Exception $exception) {
        halt('參數錯誤的異常'); # 處理錯誤1
    }
}

在咱們初期的學習和開發當前,以上的方法的確是可行的,相似於咱們經常說的 JQ一把梭 , 上面的即是 錯誤一刀切,這種方式的確是可以進行解決問題, 可是很是不利於針對某個錯誤的處理thinkphp

例如 BadRequest400Exception 方法拋出的 getMessage() 的結果是json數據, 而 BadMethodCallException 拋出的是字符串數據 你告訴我怎麼處理呢?json

因此, 衍生了以下的第二種處理錯誤方式php7

2) try catch (2)app

代碼案例:框架

public function addIndex($data)
{
    $params = [
        'index' => 'show',
        'type' => 'store',
        'body' => $data
    ];

    try{
        $this->client->index($params);
    }catch (BadRequest400Exception $e) {
        $msg = json_decode($e->getMessage(), true);
        halt('參數錯誤的異常:' . $msg); # 處理錯誤1
    }catch (BadMethodCallException $e){
        halt('方法不存在的錯誤異常:' . $e->getMessage()); # 處理錯誤2
    }catch(\Exception $e) {
        halt('系統異常');
    }
}

恩, 的確,以上也算是咱們日常常見的一種解決方式, 可是, 有沒有考慮過代碼冗餘呢, 若是10個須要捕獲錯誤的方法, 難道就寫10個嗎 . 這個時候,咱們須要一個 耦合掉這些方法的操做 , 儘管php7中錯誤異常的支持愈來愈強大, 但面對業務場景的不斷增長,咱們必須採起 可靠 穩定 瞭解 的三種標準來要求它. 瞭解的意思是, 可以給咱們輸出清楚的錯誤日誌等

步入正題

開發框架是thinkphp5.1

  • 異常處理接管

thinkphp給咱們提供了強大的異常處理機制, 固然市場上的框架基本都能提供這個功能. 咱們找到文檔錯誤處理的那一塊, (https://www.kancloud.cn/manua...[https://www.kancloud.cn/manual/thinkphp5_1/354092#_42]

基於tp自帶的, 再其基礎上進行優化更加豐富的自定義異常處理, 這即是我所理解的異常處理接管吧

進行業務設計

定義個異常處理接管, 重寫render方法 , 這個方法主要是拋出一個響應異常 .

注意注意 ~~!!

查看源碼還有個人經驗得知, 它必須是 return 的方式 , 返回一個 Response 建立的響應異常, 不然會給你拋出莫名其妙的錯誤

閱讀以下文章, 請先查看 http://surest.cn/archives/71/這個文章

主要是瞭解一下 https://github.com/surest-sky/example/blob/master/laravel%E7%9A%84%E4%B8%80%E4%BA%9B%E5%86%99%E6%B3%95/ApiResponse_tp.php的用法

異常處理接管的代碼以下

<?php
/**
* Created by PhpStorm.
* User: 鄧塵鋒
* Date: 19-5-5
* Time: 上午11:13
*/

namespace app\common\exception;

use Exception;
use think\exception\Handle;
use think\Response;

/**
* 異常處理接管, debug關閉狀態下防止拋出500錯誤
* 如下狀況將會致使500錯誤拋出
* 致命錯誤 | 系統嚴重錯誤 | 代碼的嚴重問題
* Class Handler
* @package app\common\exception
*/
class Handler extends Handle
{
    # 這裏寫須要進行捕獲的錯誤加載類庫
    protected $handler_exceptions = [
        '\app\common\exception\EsearchHandler',
        '\app\common\exception\SystemHandler',
    ];

    // 重寫render方法
    public function render(Exception $e)
    {
        try {
            $isDebug = config('app.app_debug'); # 判斷是不是斷點模式

//            # 走系統拋出的系統
//            if( !request()->isAjax() || $isDebug) {
//                return parent::render($e);
//            }

            # 錯誤的信息, 用於寫入日誌
            $error_info = [
                'code' => $e->getCode(), # 錯誤代碼描述
                'line' => $e->getLine(), # 錯誤代碼行
                'message' => $e->getMessage(), # 錯誤詳細信息
                'file' => $e->getFile() # 錯誤文件名稱
            ];

        # 捕獲錯誤處理異常
        return $this->handler_exception($e, $error_info);

        }catch (\Exception $exception) {
            return parent::render($exception);
        }
    }

    /**
    * 加載錯誤處理
    * @param $e
    */
    public function handler_exception($e, $error_info)
    {
        foreach ($this->handler_exceptions as $exception) {
            $exception = new $exception;
            if($exception->handler($e, $error_info) instanceof Response){
                return $exception->handler($e, $error_info);
            }
        }
    }
}

它的流程大概是

  • handler_exceptions: 定義須要捕獲異常的錯誤處理模塊
  • render : 重寫錯誤處理異常

    error_info 用來分發給各個子模塊異常, 用來須要的地方進行打印日誌

    catch (\Exception $exception) : 這個的用處不大, 主要是這個若是出現錯誤的話,本身處理不了, 就交給tp自帶的異常處理去處理 return parent::render($exception); 執行父方法

  • handler_exception

    這個就是, 來遍歷執行是否須要捕獲的錯誤的模塊, 依次加載, 當真實的響應了錯誤的時候(會繼承Response方法), 就代表的確是發生錯誤了, 具體看下面

功能模塊的異常處理

這裏查看 elasticSearch 的方法

<?php
/**
* Created by PhpStorm.
* User: 鄧塵鋒
* Date: 19-5-13
* Time: 上午9:32
*/

namespace app\common\exception;


// 用戶接受處理ElasticSearch處理的一些錯誤

class EsearchHandler extends BaseException implements CustomExceptionInterface
{
    public function handler($e, array $error_info)
    {
        $e_class = get_class($e);

        switch ($e_class) {
            case 'Elasticsearch\Common\Exceptions\UnexpectedValueException':
                return $this->showMsg($e->getMessage(), $error_info);
                break;
            case 'Elasticsearch\Common\Exceptions\BadRequest400Exception' :
                return $this->showMsg(json_decode($e->getMessage(), true), $error_info);
                break;
        }

        # 不然不返回錯誤異常
    }
}

流程解釋, 如上能夠看到, 咱們繼承了 BaseException 而且實現了 CustomExceptionInterface 接口

  • BaseException

    namespace appcommonexception;
    use appcommonTraitsApiResponse;
    class BaseException
    {

    use ApiResponse;
    
    public function __construct()
    {
        # 這個必須強制設置爲true
        $this->is_anomaly_andling_takeover = true;
    
        # 檢查當前異常處理是否繼承了異常處理接管, 沒有則拋出一個異常
        if(!($this instanceof CustomExceptionInterface)) {
            return $this->showMsg(__CLASS__ . '必須繼承CustomExceptionInterface這個接口', []);
        }
    }
    
    public function showMsg($msg, array $error_info, $code = 500)
    {
        return $this->status($msg, compact('error_info'), $code, $code);
    }

    }

  • CustomExceptionInterface

    <?php
    /**

    • Created by PhpStorm.
    • User: 鄧塵鋒
    • Date: 19-5-13
    • Time: 上午9:35
*/
        namespace app\common\exception;
        /**
        * 定義一個異常處理接口, 只要是app\common\exception下的子類, 必須繼承它
        * Interface CustomExceptionInterface
        * @package app\common\exception
        */
        Interface CustomExceptionInterface {
            public function handler($e, array $error_info); # 接受異常處理

            public function showMsg($msg, array $error_info, $code); # 拋出錯誤消息
        }

流程解釋 :

BaseException 使用了 ApiResponse 用於拋出異常,

定義的 showMsg 是爲了實現 CustomExceptionInterface 接口, 做用在於返回一個 response 的錯誤

$this->is_anomaly_andling_takeover 這個是爲了重寫定義 ApiResponse 的響應, 在原先的 ApiResponse 中

public function respond($data, $header = [])
    {
        $type = request()->isAjax() ? 'json' : "html";

        $response = JsonResponse::create($data, $type, $this->code, $header);

        throw new HttpResponseException($response);
    }

他是直接拋出的 HttpResponseException 異常的錯誤, 顯然不符合咱們以前所說的 它必須是 return 的方式 , 返回一個 Response 建立的響應異常, 不然會給你拋出莫名其妙的錯誤, 因此從新定義屬性

public function respond($data, $header = [])
{

    $type = request()->isAjax() ? 'json' : "html";

    $response = JsonResponse::create($data, $type, $this->code, $header);

    if( $this->is_anomaly_andling_takeover ) {
        return $response; # 拋出 response 異常
    }
    throw new HttpResponseException($response);
}

這樣 showMsg 方法就返回的是 response 異常了

在子類 handler 方法中, 就能夠輕鬆的定義你的錯誤異常咯

public function handler($e, array $error_info)
    {
        $e_class = get_class($e);

        switch ($e_class) {
            case 'Elasticsearch\Common\Exceptions\UnexpectedValueException':
                return $this->showMsg($e->getMessage(), $error_info);
                break;
            case 'Elasticsearch\Common\Exceptions\BadRequest400Exception' :
                return $this->showMsg(json_decode($e->getMessage(), true), $error_info);
                break;
        }

        # 不然不返回錯誤異常
    }

如以前所說的, 咱們能夠把添加因此那一串代碼演化成

public function addIndex($data)
{
    $params = [
        'index' => 'show',
        'type' => 'store',
        'body' => $data
    ];

    $this->client->index($params);

}

不再須要進行捕獲了, 若是它拋出錯誤了, 會自動走到 handler 中, 而且響應一個你定義的異常

再添加一個錯誤處理

衆所周知, 他是路由出現的異常問題是不可避免的, 咱們來這樣定義

app\common\exception\Handler 屬性 handler_exceptions 中添加 '\app\common\exception\RouteExceptionHandler', 而且定義他

...

use think\exception\HttpException;

class RouteExceptionHandler extends BaseException implements CustomExceptionInterface
{

public function handler($e, array $err_info)
{
    # 檢測理由錯誤
    if( $e instanceof HttpException) {
        return $this->showMsg("當前請求路由不存在", $err_info, 404);
    }
}

便可

響應結果:

{
    "msg": "當前請求路由不存在",
    "code": 404,
    "error_info": {
        "code": 0,
        "line": 63,
        "message": "module not exists:store",
        "file": "/www/wwwroot/app/thinkphp/library/think/route/dispatch/Module.php"
    }
}

完結

代碼實例在

https://github.com/surest-sky/example/tree/master/exception

來源

鄧塵鋒

相關文章
相關標籤/搜索