關於PHP自動捕捉處理錯誤和異常的嘗試


  之所以想着做錯誤和異常的自動處理是因為:

    用的公司自己的框架寫API,沒有異常和錯誤相關功能,

    而每次操作都進行try...catch,有點繁瑣不說,感覺還很雞肋,即使我catch到了,還是得寫代碼進行處理,哪怕封裝了一個方法進行處理也還是繁瑣,

    這種情況應該是程序自動進行處理,不該是這樣弱智的人工try...catch,然后處理

    以及其他同事寫代碼時並不能嚴格的進行try...catch

    於是乎,想自己設計一個自動捕獲並處理錯誤和異常的功能。

  發現了一篇深度好文:https://www.cnblogs.com/zyf-zhaoyafei/p/6928149.html

     理解了PHP中自帶的  register_shutdown_function,set_error_handler,set_exception_handler這三個函數

  通過這三個函數我們可以實現PHP 自動捕獲異常和錯誤。PHP5也不支持ERROR的捕獲,只能try...catch (Exception) 

  三個函數必須理解,如果你想實現該功能,希望你可以先做個小demo體驗下三個函數的神奇之處

  這三個函數需要你寫在框架的生命周期比較靠前的地方,如果不知道寫哪里,請寫在入口文件中,或者在入口文件require一下(下面的源碼可以直接require使用

  因為如果寫到其他文件中,而碰巧該文件有錯誤或者異常,那么這三個函數就不發生作用了,而入口文件我們都可以確保正確

  正文:

      首先介紹set_error_handler這個函數,這個函數用於捕獲低級別的錯誤,該函數只能捕獲系統產生的一些Warning、Notice級別的錯誤,並調用一個用戶自定義的錯誤處理函數。

    比如在入口文件寫入  set_error_handler('low_level_error'),並且我們建立了low_level_error 自定義方法來進行日志的記錄 。

    當發生Warning、Notice級別的錯誤時,系統會自動調用low_level_error方法,四個參數"type"、 "message"、"file" 和 "line"也是系統傳過來的,可以直接使用。

    注意:使用此函數后  error_reporting ( E_ALL )  無法提示warning和notice級別的警告, 如果本地開發,最好關閉此函數,在生產環境才使用;  使用 其它兩個函數 時錯誤提示信息不受影響

 

    可以使用下面源碼中的單個方法進行測試, 只需echo $str; exit; 就好

      第二個函數 register_shutdown_function, 這個函數與第一個函數搭配使用,可以捕捉到Fatal Error、Parse Error等高級別會讓程序中止的錯誤,

    這個方法是PHP腳本執行結束前最后一個調用的函數,比如腳本錯誤、die()、exit、異常、正常結束都會調用。這個方法沒有接受參數,所以需要借助error_get_last() ,error_get_last函數可以獲取到

    系統最近發生的一個錯誤,同樣包含  "type"、 "message"、"file" 和 "line"四個字段,需要注意的是 本函數捕捉到的 (2,8,32,128,512,1024,2048,8192)等錯誤都是警告級別的,自己酌情處理。

 

      第三個函數set_exception_handler, 這個函數可以捕獲 沒有用try/catch塊來捕獲的異常,並且在回調函數調用后異常會中止(這個我沒有試,因為我捕捉異常后直接返回給前端錯誤code碼了)。

  思路:通過set_error_handler 和 register_shutdown_function 兩個函數可以捕捉到所有的錯誤,通過set_exception_handler 捕捉所有未處理的異常,這樣程序中所有的錯誤和異常就被我們捕捉到了,

    接下來,我們可以通過自定義函數對它們進行操作,由於我的業務是直接向前端輸出,因此只是記錄相關日志后直接echo並退出,大家可以在自定義函數內自己決定做什么處理。

    還可以在兩個自定義的錯誤處理函數中再拋出異常,由自定義的異常處理類統一處理,這也是一個不錯的思路,大家可以嘗試下。

  最后:

    下面是相關的代碼,供大家參考和測試。

 

<?php
/**引入本文件后,PHP會自動處理錯誤和異常, php版本5.6, php7+版本需要小調整,運行時根據提示調整即可
 * 本方法直接捕捉錯誤並記錄日志,如有興趣,可以捕捉到錯誤后再次拋出異常,將錯誤轉為異常后,由set_exception_handler統一進行處理
 * User: LiZheng  271648298@qq.com
 * Date: 2019/3/21
 */
register_shutdown_function('high_level_error');   //使用了register_shutdown_function 后,當程序遇見一些致命錯誤時會自動調用函數 high_level_error
set_error_handler('low_level_error');        //使用了set_error_handler 后,當程序遇見Notice 和Warning錯誤時會自動調用函數 low_level_error
set_exception_handler('exception_log'); //使用了set_exception_handler后,當遇到所有的未捕獲的異常時會自動調用函數 exception_log
/**
 * 該方法只能捕獲系統產生的一些Warning、Notice級別的錯誤
 * @param $type
 * @param $message
 * @param $file
 * @param $line
 * User: LiZheng  271648298@qq.com
 * Date: 2019/3/21
 */
function low_level_error($type, $message, $file, $line)
{
    $time = date('Y-m-d H:i:s');
    //拼接要記錄成日志的信息
    $str = "\n ".$time.' set_error_handler: ' . exception_self::get_type($type) . ':' . $message . ' in ' . $file . ' on ' . $line . ' line .';
    $str = addslashes($str);
    //記錄日志    error_log是PHP自帶函數,記錄到指定位置,詳情見官網
    error_log($str,3,ROOT_DIR."/runtime/logs/php_error.log");  //ROOT_DIR為項目根目錄
    //對於notice和warning 級別的錯誤,只進行記錄而不會終止程序(但在本地開發時希望提示,可以通過if語句判斷)
    //echo json_encode(array('code'=>“ERR_ERROR”, 'msg'=>$time, 'data'=>array()), true);exit;
}

/**
 * 捕捉一些致命錯誤
 * User: LiZheng  271648298@qq.com
 * Date: 2019/3/21
 */
function high_level_error()
{
    //error_get_last() 獲取最近一條發生的錯誤,包含"type"、 "message"、"file" 和 "line"
    if ($error = error_get_last()) {
        $time = date('Y-m-d H:i:s');
        //拼接要記錄成日志的信息
        $str = "\n ".$time.' register_shutdown_function: Type:' . exception_self::get_type($error['type']) . ' Msg: ' . $error['message'] . ' in ' . $error['file'] . ' on line ' . $error['line'];
        $str =  addslashes($str);
        //記錄日志    error_log是PHP自帶函數,記錄到指定位置,還可以發送郵件以及其他操作
        error_log($str,3,ROOT_DIR."/runtime/logs/php_error.log");   //ROOT_DIR為項目根目錄
        //直接輸出接口的返回信息, 給前端一個錯誤碼code, msg返回時間用於方便查找日志
        //echo json_encode(array('code'=>“ERR_ERROR”, 'msg'=>$time, 'data'=>array()), true);exit;
        if(in_array($error['type'], array(2,8,32,128,512,1024,2048,8192)))//本條件中的錯誤均為非致命錯誤,  8192 錯誤是棄用提示
        {
//            if(APP_ENV == 'LOCAL')  //這里可以對本地開發環境時進行不同的處理
//            {
//                header('Content-Type: text/html; charset=utf-8');
//                echo $str."\n";
//            }
        }else
        {
//            if(APP_ENV == 'LOCAL')
//            {
//                header('Content-Type: text/html; charset=utf-8');
//                echo $str."\n";
//            }
            //直接輸出接口的返回信息, 給前端一個錯誤碼code, msg返回時間用於方便查找日志
            echo json_encode(array('code'=>ERR_ERROR, 'msg'=>$time, 'data'=>array()), true);exit;
        }
    }
}

/**
 * 捕捉異常
 * @param $exception
 * User: LiZheng  271648298@qq.com
 * Date: 2019/3/21
 */
function exception_log($exception)
{
    $time = date('Y-m-d H:i:s');
    //拼接要記錄成日志的信息
    $str = "\n ".$time." set_exception_handler: Exception: ". $exception->getMessage()." in ".$exception->getFile()." on line ". $exception->getLine();
    $str =  addslashes($str);
    //記錄日志
    error_log($str,3,ROOT_DIR."/runtime/logs/php_error.log");
    //輸出信息
    echo json_encode(array('code'=>“ERR_EXCEPTION”, 'msg'=>$time, 'data'=>array()), true);exit;
}

/**
 * Class error_handler
 * Create on 2019/3/21 14:15
 * User: LiZheng  271648298@qq.com
 * Date: 2019/3/21
 */
class exception_self extends Exception
{
    /**
     * 構造函數
     * @param string $message
     * @param string $code
     */
    public function __construct($message=null, $code=null)
    {
        parent::__construct($message, $code);
//        self::log($this->__toString());
    }
    //PHP的錯誤級別
    public static $type =  array(
        '1' => 'E_ERROR ',
        '2' => 'E_WARNING  ',
        '4' => 'E_PARSE  ',
        '8' => 'E_NOTICE  ',
        '16' => 'E_CORE_ERROR  ',
        '32' => 'E_CORE_WARNING  ',
        '64' => 'E_COMPILE_ERROR  ',
        '128' => 'E_COMPILE_WARNING  ',
        '256' => 'E_USER_ERROR  ',
        '512' => 'E_USER_WARNING  ',
        '1024' => 'E_USER_NOTICE  ',
        '2048' => 'E_STRICT  ',
        '4096' => 'E_RECOVERABLE_ERROR  ',
        '8191' => 'E_ALL  ',
    );

    /**
     * 通過數字 置換 對應的英文
     * @param $key
     * @return mixed
     * User: LiZheng  271648298@qq.com
     * Date: 2019/3/21
     */
    public static function get_type($key)
    {
        if(isset(self::$type[$key]))
        {
            return self::$type[$key];
        }else
        {
            return $key;
        }
    }
}

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM