那些年,PHPer遇到的錯誤與異常:下篇之異常

上一篇:那些年,PHPer遇到的錯誤與異常:上篇之錯誤

1、PHP中的異常簡介及使用

1.1 異常執行流程

try
{
    // 須要進行異常處理的代碼段;
    throw 語句拋出異常;
}catch( Exception $e )
{
    ... 
}
catch( Exception $e )
{
    // 處理異常
}
contine.....

     未被捕獲的異常會報致命錯誤:Fatal error:Uncaught exception.....php

1.2 PHP異常特色

  1. PHP不會主動捕獲異常,須要程序中主動拋出 (throw)異常,才能捕獲。
  2. throw會自動向上拋出
  3. throw以後的語句不會執行
  4. try後必須有catch,不然解析錯誤Parse error
try{
    $num1=3;
    $num2=0;
    if($num2==0){
        throw new Exception('0不能看成除數');
        echo 'this is a test';//看不到
    }else{
        $res=$num1/$num2;
    }
}catch(Exception $e){
    echo $e->getMessage();
}

1.3 PHP內置異常

Php不像java提供了不少異常類,因此不少異常都會當成錯誤。要想變錯誤爲拋出異常,須要手動throw異常對象html

PHP內置異常如:PDOExceptionSplFileObject 能夠自動拋出異常,後面的代碼能夠繼續執行。java

clipboard.png

clipboard.png

1.4 錯誤和異常的區別

1.4.1 異常處理

     當異常被拋出,throw後的代碼不會繼續執行PHP 會嘗試查找匹配的 catch 代碼塊。若是異常沒有被捕獲,並且又沒用使用set_exception_handler() 做相應的處理的話,那麼將發生一個嚴重的錯誤(致命錯誤),而且輸出 「Uncaught Exception」 (未捕獲異常)的錯誤消息。mysql

1.4.2 異常的基本語法結構

     try - 須要進行異常處理的代碼應該放入try代碼塊內,以便捕獲潛在的異常。若是沒有觸發異常,則代碼將照常繼續執行。可是若是異常被觸發,會拋出一個異常
     throw - 這裏規定如何觸發異常。每個trythrow 必須對應至少一個 catch。使用多個catch代碼塊能夠捕獲不一樣種類的異常。
     catch - catch代碼塊會捕獲異常,並建立一個包含異常信息的對象程序員

1.4.3 從新拋出異常

     有時,當異常被拋出時,也許但願以不一樣於標準的方式對它進行處理。能夠在一個 catch 代碼塊中再次拋出異常。注意再次拋出異常須要try{}catch{},不能直接在catch代碼塊中throw異常sql

     腳本應該對用戶隱藏系統錯誤。對程序員來講,系統錯誤也許很重要,可是用戶對它們並不感興趣。爲了讓用戶更容易使用,您能夠再次拋出帶有對用戶比較友好的消息的異常。
     簡而言之:若是拋出了異常,就必須捕獲它。數據庫

1.4.4 錯誤與異常的區別

異常:程序運行與預期不太一致

錯誤:觸發的是自己的錯誤segmentfault

  • 當遇到錯誤的時候,觸發的是自己的錯誤,不會自動的拋出異常。異常能夠經過throw語句拋出異常,經過catch捕獲異常,若是未捕獲會產生致命錯誤。
  • 錯誤在發生的時候或觸發的時候,必須立刻對腳本進行處理。異常能夠一一貫上傳遞,直到被捕獲,再處理。
  • 錯誤觸發不具備相關代碼或名稱。異常能夠自定義處理錯誤信息(異常的好處就體現出來了),是經過代碼來拋出,捕獲而後處理

2、自定義異常類

2.1 自定義異常類

  1. 自定義異常類只能重寫構造函數和toString兩個函數
  2. 自定義異常類能夠增長本身的方法
  3. 多個catch 時,通常Exception基類放在最後,基類能夠調用自定義異常類定義的方法
/**
 * 自定義異常類
 * Class MyException
 */
class MyException extends Exception
{
    public function __construct($message = "", $code = 0, Throwable $previous = null)
    {
        parent::__construct($message, $code, $previous);
    }
    public function __toString()
    {
        $message = "<h2>出現異常了,信息以下</h2>";
        $message .= "<p>".__CLASS__."[{$this->code}]:{$this->message}</p>";
        return $message;
    }
    public function test()
    {
        echo 'this is a test';
    }
    public function stop()
    {
        exit('script end...');
    }
    //自定義其它方法
}

try{
    echo '出現異常啦';
    throw new MyException('測試自定義異常');
}catch (MyException $exception){
    echo $exception->getMessage();
    echo $exception;
}
//會繼續執行
echo 'continue.........';
try{
    throw new MyException('測試自定義異常');
}catch (Exception $exception){
    echo $exception->getMessage();
    $exception->test();
} catch (MyException $exception){
    echo $exception->getMessage();
}

2.2 小技巧

//將錯誤用錯誤抑制符吸取,而後拋出異常
If(@!fwrite($filename,$data)) throw new exception(自定義異常)

PHP_EOL #換行符

     記錄錯誤日誌信息方式:緩存

     (1) :file_put_contents(LOG_PATH.'error.log';, '錯誤信息'.' '.date('Y-m-d H:i:s')."\r\n", FILE_APPEND);服務器

     (2) :error_log('錯誤信息'.' '.date('Y-m-d H:i:s')."\r\n",3,LOG_PATH.'error.log');

2.3 使用觀察者模式處理異常信息

Exception_Observer.php

/**
 * 給觀察者定義規範
 *
 * Interface Exception_Observer
 */
interface Exception_Observer
{
    public function update(Observable_Exception $e);
}
Observable_Exception.php

/**
 * 定義觀察者
 * Class Observable_Exception
 */
class Observable_Exception extends Exception
{
    //保存觀察者信息
    public static $_observers = array();
    public static function attach(Exception_Observer $observer)
    {
        self::$_observers[] = $observer;
    }
    public function __construct($message = "", $code = 0, Throwable $previous = null)
    {
        parent::__construct($message, $code, $previous);
        $this->notify();
    }
    public function notify()
    {
        foreach (self::$_observers as $observer) {
            $observer->update($this);
        }
    }
}
Logging_Exception_Observer.php

/**
 * 記錄錯誤日誌
 * Class Logging_Exception_Observer
 */
class Logging_Exception_Observer implements Exception_Observer
{
    protected $_filename = __DIR__.'/error_observer.log';
    public function __construct($filename = null)
    {
        if ($filename!==null && is_string($filename)){
            $this->_filename = $filename;
        }
    }

    public function update(Observable_Exception $e)
    {
        $message = "時間:".date('Y:m:d H:i:s',time()).PHP_EOL;
        $message.= "信息:".$e->getMessage().PHP_EOL;
        $message.= "追蹤信息:".$e->getTraceAsString().PHP_EOL;
        $message.= "文件:".$e->getFile().PHP_EOL;
        $message.= "行號:".$e->getLine().PHP_EOL;
        error_log($message,3,$this->_filename);//寫到日誌中
    }
}
test.php

/**
 *測試
 */
header('content-type:text/html;charset=utf-8');
require_once 'Exception_Observer.php';
require_once 'Logging_Exception_Observer.php';
require_once 'Observable_Exception.php';

Observable_Exception::attach(new Logging_Exception_Observer());

class MyException extends Observable_Exception{
    public function test()
    {
        echo 'this is a test';
    }
}

try{
    throw new MyException('出現了異常!');
}catch (MyException $exception){
    echo $exception->getMessage();
}

3、自定義異常處理器

3.1 如何自定義異常處理器

3.1.1 自定義異常處理器

  1. 相似set_error_handler接管系統的錯誤處理函數,set_exception_handler接管全部沒有被catch的異常
  2. restore_exception_handlerrestore_error_handler同樣,本質上應該說從異常/錯誤處理函數棧中彈出一個。好比有一個異常處理函數,彈出一個的話,就沒有異常處理函數,若是有異常沒有捕獲,會交由錯誤處理函數,如沒有錯誤處理函數,異常最終會有系統錯誤處理函數處理。若是設置了2個異常處理函數,彈出一個,會交由下面一個異常處理函數處理。
/**
 * 自定義異常函數處理器
 */
header('content-type:text/html;charset=utf-8');
function exceptionHandler_1($e)
{
    echo '自定義異常處理器1<br/>函數名:'.__FUNCTION__.PHP_EOL;
    echo '異常信息:'.$e->getMessage();
}
function exceptionHandler_2($e)
{
    echo '自定義異常處理器2<br/>函數名:'.__FUNCTION__.PHP_EOL;
    echo '異常信息:'.$e->getMessage();
}

set_exception_handler('exceptionHandler_1');
//set_exception_handler('exceptionHandler_2');
//恢復到上一次定義過的異常處理函數,即exceptionHandler_1
//restore_exception_handler();
//致命錯誤信息
//restore_exception_handler();
throw new Exception('測試自定義異常處理器');

//自定義異常處理器,不會向下繼續執行,由於throw以後不會再繼續執行;try{} catch{}以後,會繼續執行
//回顧:自定義錯誤處理器會繼續執行代碼,而手動拋出的錯誤信息不會繼續執行
echo 'test';
/**
 * 自定義異常類處理器
 * Class ExceptionHandler
 */

class ExceptionHandler
{
    protected $_exception;
    protected $_logFile = __DIR__.'/exception_handle.log';
    public function __construct(Exception $e)
    {
        $this->_exception = $e;
    }
    public static function handle(Exception $e)
    {
        $self = new self($e);
        $self->log();
        echo $self;
    }
    public function log()
    {
        error_log($this->_exception->getMessage().PHP_EOL,3,$this->_logFile);
    }

    /**
     * 魔術方法__toString()
     * 快速獲取對象的字符串信息的便捷方式,直接輸出對象引用時自動調用的方法。
     * @return string
     */
    public function __toString()
    {
        $message = <<<EOF
        <!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>Title</title>
        </head>
        <body>
            <h1>出現異常了啊啊啊啊</h1>
        </body>
        </html>
EOF;
    return $message;
    }

}
set_exception_handler(array('ExceptionHandler','handle'));
/**
 * try catch不會被自定義異常處理!!!!
 */
try{
    throw new Exception('this is a test');
}catch (Exception $exception) {
    echo $exception->getMessage();
}
throw new Exception('測試自定義的異常處理器');

3.1.2 錯誤/異常以後是否繼續執行代碼問題總結

異常:

自定義異常處理器不會向下繼續執行,由於 throw以後不會再繼續執行
try{} catch{}以後,會繼續執行

錯誤:

自定義錯誤處理器會繼續執行代碼,而手動拋出的錯誤信息不會繼續執行

3.2 像處理異常同樣處理PHP錯誤

3.2.1 方式一:ErrorException

/**
 * 方式一:ErrorException錯誤異常類
 * @param $errno
 * @param $errstr
 * @param $errfile
 * @param $errline
 * @throws ErrorException
 */
function exception_error_handler($errno,$errstr,$errfile,$errline){

    throw new ErrorException($errstr,0,$errno,$errfile,$errline);
}

set_error_handler('exception_error_handler');

try{
    echo gettype();
}catch (Exception $exception){
    echo $exception->getMessage();
}

3.2.2 方式二:自定義異常類,繼承基類Exception

/**
 * 方式二:自定義異常類
 * Class ErrorToException
 */
//顯示全部的錯誤
error_reporting(-1);
class ErrorToException extends Exception{
    public static function handle($errno,$errstr)
    {
        throw new self($errstr,0);
    }
}

set_error_handler(array('ErrorToException','handle'));
set_error_handler(array('ErrorToException','handle'),E_USER_WARNING|E_WARNING);

try{
    echo $test;//notice,不會被處理
    echo gettype();//warning
    //手動觸發錯誤
    trigger_error('test',E_USER_WARNING);
}catch (Exception $exception){
    echo $exception->getMessage();
}

3.3 PHP頁面重定向實現

header('Content-type:text/html;charset=utf-8');
class ExceptionRedirectHandler{
    protected $_exception;
    protected $_logFile = __DIR__.'redirect.log';
    public $redirect='404.html';
    public function __construct(Exception $e){
        $this->_exception=$e;
    }
    public static function handle(Exception $e){
        $self=new self($e);
        $self->log();
        // ob_end_clean()清除全部的輸出緩衝,最後沒有緩存的時候會產生通知級別的錯誤
        while(@ob_end_clean());
        header('HTTP/1.1 307 Temporary Redirect'); //臨時重定向
        header('Cache-Control:no-cache,must-revalidate');//no-cache強制向源服務器再次驗證,must-revalidate可緩存但必須再向源服務器進行確認
        header('Expires: Sat, 28 Mar 2016 13:28:48 GMT'); //資源失效的時間
        header('Location:'.$self->redirect); //跳轉
    }
    public function log(){
        error_log($this->_exception->getMessage().PHP_EOL,3,$this->_logFile);
    }
}
set_exception_handler(array('ExceptionRedirectHandler','handle'));
$link=@mysqli_connect('127.0.0.1','root','1234561');
if(!$link){
    throw new Exception('數據庫鏈接出錯啦');
}

完!

參考課程視頻:那些年你遇到的錯誤與異常

相關文章
相關標籤/搜索