1. 程式人生 > >再談PHP錯誤與異常處理

再談PHP錯誤與異常處理

  部落格好久沒有更新了,實在慚愧,最近在忙人生大事,哈哈!這段時間沒有看什麼新的東西,結合專案中遇到的PHP異常處理問題,我又重新梳理了之前模糊的概念,希望對大家理解PHP異常處理有所幫助。

  請一定要注意,沒有特殊說明:本例 PHP Version < 7
  說起PHP異常處理,大家首先會想到try-catch,那好,我們先看一段程式吧:有一個test.php檔案,有一段簡單的PHP程式,內容如下,然後命令列執行:php test.php

1 <?php
2     $num = 0;
3     try {
4         echo 1/$num;
5
6 } catch
(Exception $e){ 7 echo $e->getMessage(); 8 } 9 ?>

  我的問題是:這段程式能正確的捕捉到除0的錯誤資訊嗎?
  如果你回答能,那你就把這篇文章看完吧!應該能學點東西。

本文章分5個部分介紹我的異常處理的理解:

一、異常與錯誤的概述
  PHP中什麼是異常:
  程式在執行中出現不符合預期的情況,允許發生(你也不想讓他出現不正常的情況)但他是一種不正常的情況,按照我們的正常邏輯本不該出的錯誤,但仍然會出現的錯誤,屬於邏輯和業務流程的錯誤,而不是編譯或者語法上的錯誤。

  PHP中什麼是錯誤:
  屬於php指令碼自身的問題,大部分情況是由錯誤的語法,伺服器環境導致,使得編譯器無法通過檢查,甚至無法執行的情況。warning、notice都是錯誤,只是他們的級別不同而已,並且錯誤是不能被try-catch捕獲的。

  上面的說法是有前提條件的:
  在PHP中,因為在其他語言中就不能這樣下結論了,也就是說異常和錯誤的說法在不同的語言有不同的說法。在PHP中任何自身的錯誤或者是非正常的程式碼都會當做錯誤對待,並不會以異常的形式丟擲,但是也有一些情況會當做異常和錯誤同時丟擲(據說是,我沒有找到合適的例子)。也就是說,你想在資料庫連線失敗的時候自動捕獲異常是行不通的,因為這就不是異常,是錯誤。但是在java中就不一樣了,他會把很多和預期不一致的行為當做異常來進行捕獲。

  PHP異常處理很雞肋?
  在上面的分析中我們可以看出,PHP並不能主動的丟擲異常,但是你可以手動丟擲異常,這就很無語了,如果你知道哪裡會出問題,你新增if else解決不就行了嗎,為啥還要手動丟擲異常,既然能手動丟擲就證明這個不是異常,而是意料之中。以我的理解,這就是PHP異常處理雞肋的地方(不一定對啊)。所以PHP的異常機制不是那麼的完美,但是使用過框架的同學都知道有這個情況:你在框架中直接寫開頭那段php“自動”捕獲異常的程式碼是可以的,這是為什麼?看過原始碼的同學都知道框架中都會涉及三個函式:register_shutdown_function,set_error_handler,set_exception_handler後面我會重點講解著三個黑科技,通過這幾個函式我們可以實現PHP假自動捕獲異常和錯誤。

二、ERROR的級別
  只有熟悉錯誤級別才能對錯誤捕捉有更好的認識。 ERROR有不同的錯誤級別,我之前的一篇文章中有寫到:http://www.cnblogs.com/zyf-zhaoyafei/p/3649434.html
  下面我再總結性的給出這幾類錯誤級別:

 1     Fatal Error:致命錯誤(指令碼終止執行)
 2         E_ERROR         // 致命的執行錯誤,錯誤無法恢復,暫停執行指令碼
 3         E_CORE_ERROR    // PHP啟動時初始化過程中的致命錯誤
 4         E_COMPILE_ERROR // 編譯時致命性錯,就像由Zend指令碼引擎生成了一個E_ERROR
 5         E_USER_ERROR    // 自定義錯誤訊息。像用PHP函式trigger_error(錯誤型別設定為:E_USER_ERROR)
 6 
 7     Parse Error:編譯時解析錯誤,語法錯誤(指令碼終止執行)
 8         E_PARSE  //編譯時的語法解析錯誤
 9 
10     Warning Error:警告錯誤(僅給出提示資訊,指令碼不終止執行)
11         E_WARNING         // 執行時警告 (非致命錯誤)。
12         E_CORE_WARNING    // PHP初始化啟動過程中發生的警告 (非致命錯誤) 。
13         E_COMPILE_WARNING // 編譯警告
14         E_USER_WARNING    // 使用者產生的警告資訊
15 
16     Notice Error:通知錯誤(僅給出通知資訊,指令碼不終止執行)
17         E_NOTICE      // 執行時通知。表示指令碼遇到可能會表現為錯誤的情況.
18         E_USER_NOTICE // 使用者產生的通知資訊。

由此可知有5類是產生ERROR級別的錯誤,這種錯誤直接導致PHP程式退出。
  可以定義成:

1 ERROR = E_ERROR | E_CORE_ERROR |  E_COMPILE_ERROR | E_USER_ERROR | E_PARSE

三、PHP異常處理中的黑科技
  前面提到框架中是可以捕獲所有的錯誤和異常的,之所以能實現應該是使用了黑科技,哈哈!其實也不是什麼黑科技,主要是三個重要的函式:

  1:set_error_handler()
  看到這個名字估計就知道什麼意思了,這個函式用於捕獲錯誤,設定一個使用者自定義的錯誤處理函式。

1 <?php
2     set_error_handler('zyferror');
3     function zyferror($type, $message, $file, $line)
4     {
5       var_dump('<b>set_error_handler: ' . $type . ':' . $message . ' in ' . $file . ' on ' . $line . ' line .</b><br />');
6     }
7 ?>

  當程式出現錯誤的時候自動呼叫此方法,不過需要注意一下兩點:第一,如果存在該方法,相應的error_reporting()就不能在使用了。所有的錯誤都會交給自定義的函式處理。第二,此方法不能處理以下級別的錯誤:E_ERROR、 E_PARSE、 E_CORE_ERROR、 E_CORE_WARNING、 E_COMPILE_ERROR、 E_COMPILE_WARNING,set_error_handler() 函式所在檔案中產生的E_STRICT,該函式只能捕獲系統產生的一些Warning、Notice級別的錯誤。
  並且他有多種呼叫的方法:

1 <?php
2      // 直接傳函式名 NonClassFunction
3      set_error_handler('function_name'); 
4 
5      // 傳 class_name && function_name
6      set_error_handler(array('class_name', 'function_name')); 
7 ?>

   2:register_shutdown_function()
  捕獲PHP的錯誤:Fatal Error、Parse Error等,這個方法是PHP指令碼執行結束前最後一個呼叫的函式,比如指令碼錯誤、die()、exit、異常、正常結束都會呼叫,多麼牛逼的一個函式啊!通過這個函式就可以在指令碼結束前判斷這次執行是否有錯誤產生,這時就要藉助於一個函式:error_get_last();這個函式可以拿到本次執行產生的所有錯誤。error_get_last();返回的資訊:
  [type]           - 錯誤型別
  [message] - 錯誤訊息
  [file]              - 發生錯誤所在的檔案
  [line]             - 發生錯誤所在的行

1 <?php
2     register_shutdown_function('zyfshutdownfunc');
3     function zyfshutdownfunc()
4     {
5         if ($error = error_get_last()) {
6             var_dump('<b>register_shutdown_function: Type:' . $error['type'] . ' Msg: ' . $error['message'] . ' in ' . $error['file'] . ' on line ' . $error['line'] . '</b>');
7         }
8     }
9 ?>

   通過這種方法就可以巧妙的打印出程式結束前所有的錯誤資訊。但是我在測試的時候我發現並不是所有的錯誤終止後都會呼叫這個函式,可以看下面的一個測試檔案,內容是:

 1 <?php
 2     register_shutdown_function('zyfshutdownfunc');
 3     function zyfshutdownfunc()
 4     {
 5         if ($error = error_get_last()) {
 6             var_dump('<b>register_shutdown_function: Type:' . $error['type'] . ' Msg: ' . $error['message'] . ' in ' . $error['file'] . ' on line ' . $error['line'] . '</b>');
 7         }
 8     }
 9     var_dump(23+-+); //此處語法錯誤
10 ?>

   自己可以試一下,你可以看到根本就不會觸發zyfshutdownfunc()函式,其實這是一個語法錯誤,直接報了一個:

1 <?php
2     Parse error: syntax error, unexpected ')' in /www/mytest/exception/try-catch.php on line 71
3 ?>

  由此引出一個奇葩的問題:問什麼不能觸發,為什麼框架中是可以的?其實原因很簡單,只在parse-time出錯時是不會呼叫本函式的。只有在run-time出錯的時候,才會呼叫本函式,我的理解是語法檢查器前沒有執行register_shutdown_function()去把需要註冊的函式放到呼叫的堆疊中,所以就根本不會執行。那框架中為什麼任何錯誤都能進入到register_shutdown_function()中呢,其實在框架中一般會有統一的入口index.php,然後每個類庫檔案都會通過include ** 的方式載入到index.php中,相當與所有的程式都會在index.php中聚集,同樣,你寫的具有語法錯誤的檔案也會被引入到入口檔案中,這樣的話,呼叫框架,執行index.php,index.php本身並沒有語法錯誤,也就不會產生parse-time錯誤,而是 include 檔案出錯了,是run-time的時候出錯了,所以框架執行完之後就會觸發register_shutdown_function();
  所以現在可是試一下這個寫法,這樣就會觸發zyfshutdownfunc()回調了:

 1 a.php檔案
 2 <?php
 3   // 模擬語法錯誤
 4   var_dump(23+-+);
 5 ?>
 6 
 7 b.php檔案
 8 <?php
 9     register_shutdown_function('zyfshutdownfunc');
10     function zyfshutdownfunc()
11     {
12         if ($error = error_get_last()) {
13             var_dump('<b>register_shutdown_function: Type:' . $error['type'] . ' Msg: ' . $error['message'] . ' in ' . $error['file'] . ' on line ' . $error['line'] . '</b>');
14         }
15     }
16     require 'a.php';
17 ?>

   3:set_exception_handler()
  設定預設的異常處理程式,用在沒有用try/catch塊來捕獲的異常,也就是說不管你丟擲的異常有沒有人捕獲,如果沒有人捕獲就會進入到該方法中,並且在回撥函式呼叫後異常會中止。看一下用法:

1 <?php
2     set_exception_handler('zyfexception');
3     function zyfexception($exception)
4     {
5         var_dump("<b>set_exception_handler: Exception: " . $exception->getMessage()  . '</b>');
6     }
7     throw new Exception("zyf exception");
8 ?>

 四、巧妙的捕獲錯誤和異常
  1:把錯誤以異常的形式丟擲(不能完全丟擲)
    由上面的講解我們知道,php中的錯誤是不能以異常的像是捕獲的,但是我們需要讓他們丟擲,已達到擴充套件 try-catch的影響範圍,我們前面講到過set_error_handler() 方法,他是幹嘛用的,他是捕獲錯誤的,所以我們就可以藉助他來吧錯誤捕獲,然後再以異常的形式丟擲,ok,試試下面的寫法:

 1 <?php
 2     set_error_handler('zyferror');
 3     function zyferror($type, $message, $file, $line)
 4     {
 5         throw new \Exception($message . 'zyf錯誤當做異常');
 6     }
 7 
 8     $num = 0;
 9     try {
10         echo 1/$num;
11 
12     } catch (Exception $e){
13         echo $e->getMessage();
14     }
15 ?>

  好了,試一下,會打印出:

1 Division by zero zyf123

流程:本來是除0錯誤,然後觸發set_error_handler(),在set_error_handler()中相當與殺了個回馬槍,再把錯誤資訊以異常的形式丟擲來,這樣就可以實現錯誤以異常的形式丟擲。大家要注意:這樣做是有缺點的,會受到set_error_handler()函式捕獲級別的限制。

     2:捕獲所有的錯誤
        由set_error_handler()可知,他能夠捕獲一部分錯誤,不能捕獲系統級E_ERROR、E_PARSE等錯誤,但是這部分可以由register_shutdown_function()捕獲。所以兩者結合能出現很好的功能。
        看下面的程式:

 1 a.php內容:
 2 <?
 3     // 模擬Fatal error錯誤
 4     //test();
 5 
 6     // 模擬使用者產生ERROR錯誤
 7     //trigger_error('zyf-error', E_USER_ERROR);
 8 
 9     // 模擬語法錯誤
10     var_dump(23+-+);
11 
12     // 模擬Notice錯誤
13     //echo $f;
14 
15     // 模擬Warning錯誤
16     //echo '123';
17     //ob_flush();
18     //flush();
19     //header("Content-type:text/html;charset=gb2312");
20 ?>
21 b.php內容: 22 <? 23 error_reporting(0); 24 register_shutdown_function('zyfshutdownfunc'); 25 function zyfshutdownfunc() 26 { 27 if ($error = error_get_last()) { 28 var_dump('<b>register_shutdown_function: Type:' . $error['type'] . ' Msg: ' . $error['message'] . ' in ' . $error['file'] . ' on line ' . $error['line'] . '</b>'); 29 } 30 } 31 32 set_error_handler('zyferror'); 33 function zyferror($type, $message, $file, $line) 34 { 35 var_dump('<b>set_error_handler: ' . $type . ':' . $message . ' in ' . $file . ' on ' . $line . ' line .</b><br />'); 36 } 37 38 require 'a.php'; 39 ?>

   到此就可以解釋開頭的那個程式了吧,test.php 如果是單檔案執行是不能捕獲到錯誤的,如果你在框架中執行就是可以的,當然你按照我上面介紹的來擴充套件也是可以的。

五、自定義異常處理和異常巢狀

  1:自定義異常處理

  在複雜的系統中,我們往往需要自己捕獲我們需要特殊處理的異常,這些異常可能是特殊情況下丟擲的。所以我們就自己定義一個異常捕獲類,該類必須是 exception 類的一個擴充套件,該類繼承了 PHP 的 exception 類的所有屬性,並且我們可以新增自定義的函式,使用的時候其實和之前的一樣,大致寫法如下:

 1 <?php
 2     class zyfException extends Exception
 3     {
 4         public function errorzyfMessage()
 5         {
 6             return 'Error line ' . $this->getLine().' in ' . $this->getFile()
 7                 .': <b>' . $this->getMessage() . '</b> Must in (0 - 60)';
 8         }
 9     }
10 
11     $age = 10;
12     try {
13         $age = intval($age);
14         if($age > 60) {
15             throw new zyfException($age);
16         }
17 
18     } catch (zyfException $e) {
19         echo $e->errorzyfMessage();
20 
21     }
22 ?>

  2:異常巢狀

  異常巢狀是比較常見的寫法,在自定義的異常處理中,try 塊中可以定義多個異常捕獲,然後分層傳遞異常,理解和冒泡差不多,看下面的實現:

 1 <?php
 2     $age = 10;
 3     try {
 4         $age = intval($age);
 5         if($age > 60) {
 6             throw new zyfException($age);
 7         }
 8 
 9         if ($age <= 0) {
10             throw new Exception($age . ' must > 0');
11         }
12 
13     } catch (zyfException $e) {
14         echo $e->errorzyfMessage();
15 
16     } catch(Exception $e) {
17         echo $e->getMessage();
18     }
19 ?>

  當然也可以在catch中再丟擲異常給上層:

 1 <?php
 2     $age = 100;
 3     try {
 4         try {
 5             $age = intval($age);
 6             if($age > 60) {
 7                 throw new Exception($age);
 8             }
 9 
10         } catch (Exception $e) {
11             throw new zyfException($age);
12 
13         }
14 
15     } catch (zyfException $e) {
16         echo $e->errorzyfMessage();
17     }
18 ?>

六、PHP7中的異常處理
  現在寫PHP必須考慮版本情況,上面的寫法在PHP7中大部分都能實現,但是也會有不同點,在PHP7更新中有一條:更多的Error變為可捕獲的Exception,現在的PHP7實現了一個全域性的throwable介面,原來老的Exception和其中一部分Error實現了這個介面(interface),PHP7中更多的Error變為可捕獲的Exception返回給捕捉器,這樣其實和前面提到的擴充套件try-catch影響範圍一樣,但是如果不捕獲則還是按照Error對待,看下面兩個:

 1 <?php
 2     try {
 3         test();
 4 
 5     } catch(Throwable $e) {
 6         echo $e->getMessage() . ' zyf';
 7     }
 8 
 9     try {
10         test();
11 
12     } catch(Error $e) {
13         echo $e->getMessage() . ' zyf';
14     }
15 ?>

 因為PHP7實現了throwable介面,那麼就可以使用第一個這種方式來捕獲異常。又因為部分Error實現了介面,並且更多的Error變為可捕獲的Exception,那麼就可以使用第二種方式來捕獲異常。下面是在網上找的PHP7的異常層次樹:
Throwable
  Exception 異常
    ...
  Error 錯誤
    ArithmeticError 算數錯誤
      DivisionByZeroError 除數為0的錯誤
    AssertionError 宣告錯誤
    ParseError 解析錯誤
    TypeError 型別錯誤

 就寫到這吧,寫得手疼,關於錯誤和異常處理的大致就寫這麼多,有什麼錯誤請在評論中給出,多謝大家。

注意: 2、本文屬原創內容,為了尊重他人勞動,轉載請註明本文地址:

相關推薦

PHP錯誤異常處理

  部落格好久沒有更新了,實在慚愧,最近在忙人生大事,哈哈!這段時間沒有看什麼新的東西,結合專案中遇到的PHP異常處理問題,我又重新梳理了之前模糊的概念,希望對大家理解PHP異常處理有所幫助。   請一定要注意,沒有特殊說明:本例 PHP Version < 7  說起PHP異常處理,大家首先會想到tr

PHP 錯誤異常處理(一)

PHP 錯誤與異常處理(一) 異常與錯誤 PHP中的錯誤: php中大部分情況是由錯誤的語法,伺服器環境導致,使得編譯器無法通過檢查,甚至無法執行的情況。warning、notice都是錯誤,只是他們的級別不同而已,並且錯誤是不能被try-catch捕獲的。 PHP中的異常: 程式

PHP錯誤異常處理(一)

語法 數據 war 錯誤處理 級別 過程 應該 要求 文件 在項目開發過程中,無論你多麽仔細,都會遇到這樣或那樣的報錯,這就要求我們有個很好的處理。錯誤處理的目標:1、提升用戶體驗。2、防止數據的丟失或者程序崩潰。 php錯誤分為以下幾類1、錯誤(語法錯誤,程序直接不執行;

PHP錯誤 異常處理

函數 用戶 exceptio 產生 存放位置 如果 date error_log reporting PHP的錯誤報告有三種: 1.錯誤,語法解析錯誤,致命錯誤2.警告3.註意 錯誤 -> 致命錯誤,會終止已下程序的執行,語法錯誤的話,PHP壓根就沒執行警告 ->

【軟件構造】第七章第二節 錯誤異常處理

throw 之間 IE 程序猿 數組越界 它的 extends 希望 nds 第七章第二節 錯誤與異常處理 本節關註:Java中錯誤和異常處理的典 型技術——把原理落實到代碼上! Outline: Java中的錯誤和異常(java.lang.throwable) 異常

COM元件設計應用(十二)——錯誤異常處理

一、前言   程式設計中,錯誤處理必不可少,而且通常要佔用很大的篇幅。本回書著落在 COM 中的錯誤(異常)的處理方法。   在元件程式中,如果遇到錯誤,一般有兩個方式進行處理。   二、簡單返回   對於比較簡單的錯誤,直接返回表示錯誤原因的 HRESULT。比如下面幾個

php錯誤異常處理

現在php開發基本都使用框架,而框架一般都封裝了自己的異常處理,但是,有時候也會有比較坑的框架,只能處理業務錯誤,不僅無法追蹤系統錯誤,還遮蔽了系統錯誤,所以最近寫了一個異常處理類,大家只需要在框架的核心檔案includ

Go 初體驗 - 錯誤異常處理 - recover和panic

當前 都是 inf defer bubuko bsp 分享 def catch 先看代碼: 輸出: 內建函數panic可以讓我們人為地產生一個運行時恐慌。不過,這種致命錯誤是可以被恢復的。在Go語言中,內建函數recover就可以做到這一點。 實際上,內建函數panic

php實現例項化類後自動進行錯誤以及異常處理(簡易版)

<?php class App { public function __construct() { /* * ini_set 設定配置項 * display_errors 是否在頁面顯示錯誤資訊 *

PHP】解析PHP中的錯誤異常處理

not tty 不支持 版本 adding all 操作 lin ttr 目錄結構: contents structure [-] 錯誤級別 自定義處理器 設置異常日誌 自定義異常類 在這篇文章中,筆者將會闡述PHP中的異常處理,希望能夠對你

js中eval,arguments異常處理的用法-基礎知識總結------彭記(017)

報錯 字符串 nts 字符 number 拖動 ron cnblogs 數組 eval的使用: <script> /*eval的作用: * 1.將字符串當成js代碼來執行 * 2.可以將json格式的字符串轉換為js對象*/

php錯誤提示及查錯方法

error 設置斷點 bugs notice -- 語法 打開 tin 開始 php有哪幾種錯誤提示 1.notice : 註意 2.waring : 警告 3.error : 錯誤 PHP中都有哪幾種查錯方法? 1、語法檢查--php配置文件裏,把錯誤顯示選項都打開或者代

兄弟連學Python 錯誤異常處理

嘗試 dex 訪問 port def post 推薦 log 出現 #常見的異常 class Human: #屬性 sex = ‘man‘ age = 18 #方法 def run(self): print(‘跑

PL/SQL輕量版(三)——遊標異常處理

多個 次循環 指向 count dbms 都是 不能 weight acl 一、遊標   1.概念     遊標是一個 指向上下文的句柄( handle) 或指針。通過遊標,PL/SQL 可以控制上下文區和處理語句時上下文區會發生些什麽事情。   2.遊標處理   

python:包異常處理

word 要求 所有 expect return interrupt itl 標識 toolbar 一、包 1,什麽是包? 把解決一類問題的模塊放在同一個文件夾裏-----包 2,包是一種通過使用‘.模塊名’來組織python模塊名稱空間的方式。   1. 無論是impo

python 語法錯誤異常

問題 ber 信息 nbsp 解釋 num 了解 判斷 god 語法錯誤與異常 語法錯誤,例如: while True print(”my god“) 這面這裏錯的地方在於沒有加:冒號以及打印要換到第二行 異常,例如: num = 9 print("nummber

錯誤異常處理(7)

為什麽 nbsp 實現 str ron strong 指令 異常處理 處理 配置指令 錯誤日誌 異常處理 為什麽異常處理很方便 PHP的異常處理實現 SPL異常 錯誤和異常處理(7)

面向對象中關於元類的介紹異常處理

xxx 使用 元類 sin cor 發生 each people 信息 一、異常處理 1. 什麽是異常處理 異常是錯誤發生的信號,一旦程序出錯就會產生一個異常,如果該異常 沒有被應用程序處理,那麽該異常就會拋出來,程序的執行也隨之終止 異常包含三個部

錯誤異常處理

選項 般的 out err 語法糖 stop 請求 特性 rect Python有兩種錯誤很容易辨認:語法錯誤和異常。 1 什麽是異常? 異常即是一個事件,該事件會在程序執行過程中發生,影響了程序的正常執行。 一般情況下,在Python無法正常處理程序

python 約束異常處理

一、類的約束   1、約束就是對類的約束。其實就是父類對子類進行約束,子類必須要寫xxx方法。   2、抽象:就是當我們沒法對一個功能或者一個屬性進行精確的表述,一般都採用抽象的方式給出。     (1)抽象類的書寫規範 from abc import ABCMeta,abstractmetho