PHP基礎之錯誤與異常

上善若泪發表於2024-06-22

目錄
  • 1 錯誤
    • 1.1 簡介
    • 1.2 簡單錯誤處理
      • 1.2.1 使用die
      • 1.2.2 die和exit區別
    • 1.3 自定義錯誤處理
      • 1.3.1 定義
      • 1.3.2 建立錯誤函式
    • 1.4 觸發錯誤
    • 1.5 抑制錯誤
      • 1.5.1 行內錯誤抑制
  • 2 異常
    • 2.1 引言
    • 2.2 什麼是異常
    • 2.3 Try、throw、catch、finally
    • 2.4 自定義異常
    • 2.5 設定頂層異常處理器
  • 3 錯誤與異常總結
    • 3.1 區別

1 錯誤

1.1 簡介

PHP 中,預設的錯誤處理很簡單。一條錯誤訊息會被髮送到瀏覽器,這條訊息帶有檔名、行號以及描述錯誤的訊息。
在建立指令碼和 Web 應用程式時,錯誤處理是一個重要的部分。如果程式碼缺少錯誤檢測編碼,那麼程式看上去很不專業,也為安全風險敞開了大門。

1.2 簡單錯誤處理

1.2.1 使用die

如下例項展示了一個開啟文字檔案的簡單指令碼:

<?php
$file=fopen("welcome.txt","r");
?>

如果檔案不存在,會得到類似這樣的錯誤:

Warning: fopen(welcome.txt) [function.fopen]: failed to open stream:
No such file or directory in /www/runoob/test/test.php on line 2

為了避免使用者得到類似上面的錯誤訊息,我們在訪問檔案之前檢測該檔案是否存在:

<?php
if(!file_exists("welcome.txt"))
{
    die("檔案不存在");
}
else
{
    $file=fopen("welcome.txt","r");
}
?>

現在,如果檔案不存在,會得到類似這樣的錯誤訊息:檔案不存在
相比之前的程式碼,上面的程式碼更有效,這是由於它採用了一個簡單的錯誤處理機制在錯誤之後終止了指令碼。

1.2.2 die和exit區別

PHP 中,dieexit 是幾乎相同的功能,它們都用於終止指令碼的執行。兩者在功能上沒有本質的區別,它們都是語言結構(不是函式),都可以接受一個可選的字串引數作為退出資訊。

  • die()
    die()exit() 的別名。當呼叫 die() 時,PHP 會輸出傳遞給它的字串(如果有的話),然後終止指令碼的執行。
  • exit()
    exit() 也是用來終止指令碼的執行的。可以給它傳遞一個狀態碼(一個整數)或一個字串作為退出資訊。如果傳遞了一個字串,該字串會被輸出。
php
<?php  
exit("指令碼已終止"); // 輸出 "指令碼已終止" 然後終止  
exit(1); // 終止指令碼並返回狀態碼 1  
?>

注意事項:
如果 exit 或 die 被呼叫在一個包含有 return 語句的函式中,指令碼也會終止,但 return 語句不會被執行。但是,如果 exit 或 die 語句在 return 語句之後,那麼 exit 或 die 實際上永遠不會被執行,因為函式在 return 語句執行時就已經結束了

使用 exit 或 die 時要小心,因為它們會立即停止指令碼的執行,可能會導致資料丟失或其他未預期的副作用。在可能的情況下,最好使用更精細的控制流結構(如 if 語句、break、continue 等)來管理指令碼的流程。

1.3 自定義錯誤處理

1.3.1 定義

建立一個自定義的錯誤處理器非常簡單。建立了一個專用函式,可以在 PHP 中發生錯誤時呼叫該函式。
該函式必須有能力處理至少兩個引數 (error_level 和 error_message),但是可以接受最多五個引數(可選的:file, line-number 和 error context):

語法:
error_function(error_level,error_message,error_file,error_line,error_context)

引數 描述
error_level 必需。為使用者定義的錯誤規定錯誤報告級別。必須是一個數字
error_message 必需。為使用者定義的錯誤規定錯誤訊息
error_file 可選。規定錯誤發生的檔名
error_line 可選。規定錯誤發生的行號
error_context 可選。規定一個陣列,包含了當錯誤發生時在用的每個變數以及它們的值

錯誤報告級別
這些錯誤報告級別是使用者自定義的錯誤處理程式處理的不同型別的錯誤:

常量 描述
2 E_WARNING 非致命的 run-time 錯誤。不暫停指令碼執行
8 E_NOTICE run-time 通知。在指令碼發現可能有錯誤時發生,但也可能在指令碼正常執行時發生
256 E_USER_ERROR 致命的使用者生成的錯誤。這類似於程式設計師使用 PHP 函式 trigger_error() 設定的 E_ERROR
512 E_USER_WARNING 非致命的使用者生成的警告。這類似於程式設計師使用 PHP 函式 trigger_error() 設定的 E_WARNING
1024 E_USER_NOTICE 使用者生成的通知。這類似於程式設計師使用 PHP 函式 trigger_error() 設定的 E_NOTICE
4096 E_RECOVERABLE_ERROR 可捕獲的致命錯誤。類似 E_ERROR,但可被使用者定義的處理程式捕獲
8191 E_ALL 所有錯誤和警告。(在 PHP 5.4 中,E_STRICT 成為 E_ALL 的一部分)

1.3.2 建立錯誤函式

建立一個處理錯誤的函式:

function customError($errno, $errstr)
{
    echo "<b>Error:</b> [$errno] $errstr<br>";
    echo "指令碼結束";
    die();
}

上面的程式碼是一個簡單的錯誤處理函式。當它被觸發時,它會取得錯誤級別和錯誤訊息。然後它會輸出錯誤級別和訊息,並終止指令碼。
現在,需要確定在何時觸發該函式。

PHP 的預設錯誤處理程式是內建的錯誤處理程式。打算把上面的函式改造為指令碼執行期間的預設錯誤處理程式。可以修改錯誤處理程式,使其僅應用到某些錯誤,這樣指令碼就能以不同的方式來處理不同的錯誤。然而,在本例中,打算針對所有錯誤來使用我們自定義的錯誤處理程式:

set_error_handler("customError");

由於我們希望我們的自定義函式能處理所有錯誤,set_error_handler() 僅需要一個引數,可以新增第二個引數來規定錯誤級別
透過嘗試輸出不存在的變數,來測試這個錯誤處理程式:

<?php
// 錯誤處理函式
function customError($errno, $errstr)
{
    echo "<b>Error:</b> [$errno] $errstr";
}

// 設定錯誤處理函式
set_error_handler("customError");

// 觸發錯誤
echo($test);
?>

1.4 觸發錯誤

在指令碼中使用者輸入資料的位置,當使用者的輸入無效時觸發錯誤是很有用的。在 PHP 中,這個任務由 trigger_error() 函式完成。

在本例中,如果 "test" 變數大於 "1",就會發生錯誤:

<?php
$test=2;
if ($test>1)
{
    trigger_error("變數值必須小於等於 1");
}
?>

以上程式碼的輸出如下所示:

Notice: 變數值必須小於等於 1
in /www/test/test.php on line 5

可以在指令碼中任何位置觸發錯誤,透過新增的第二個引數,能夠規定所觸發的錯誤型別。

1.5 抑制錯誤

1.5.1 行內錯誤抑制

可以讓 PHP 利用錯誤控制運算子 @ 來抑制特定的錯誤。將這個運算子放置在表示式之前,其後的任何錯誤都不會出現。

<?php
echo @$foo['bar'];

如果 $foo['bar'] 存在,程式會將結果輸出,如果變數 $foo 或是 'bar' 鍵值不存在,則會返回 null 並且不輸出任何東西。如果不使用錯誤控制運算子,這個表示式會產生一個錯誤資訊 PHP Notice: Undefined variable: foo 或 PHP Notice: Undefined index: bar

注意PHP 處理使用 @ 的表示式比起不用時效率會低一些。
錯誤控制運算子會 完全 吃掉錯誤。不但沒有顯示,而且也不會記錄在錯誤日誌中。此外,在正式環境中 PHP 也沒有辦法關閉錯誤控制運算子。也許那些錯誤是無害的,不過那些較具傷害性的錯誤同時也會被隱藏。

2 異常

2.1 引言

異常是許多流行程式語言的標配,但它們往往被 PHP 開發人員所忽視。像 Ruby 就是一個極度重視異常的語言,無論有什麼錯誤發生,像是 HTTP 請求失敗,或者資料庫查詢有問題,甚至找不到一個圖片資源,Ruby (或是所使用的 gems),將會丟擲異常,可以透過螢幕立刻知道所發生的問題。

PHP 處理這個問題則比較隨意,呼叫 file_get_contents() 函式通常只會給出 FALSE 值和警告。許多較早的 PHP 框架比如 CodeIgniter 只是返回 false,將資訊寫入專有的日誌,或者使用類似 $this->upload->get_error() 的方法來檢視錯誤原因。這裡的問題在於必須找出錯誤所在,並且透過翻閱文件來檢視這個類使用了什麼樣的錯誤的方法,而不是明確的暴露錯誤。

2.2 什麼是異常

PHP 5 提供了一種新的物件導向的錯誤處理方法。
異常處理 用於在指定的錯誤(異常)情況發生時改變指令碼的正常流程。這種情況稱為異常。

當異常被觸發時,通常會發生:

  • 當前程式碼狀態被儲存
  • 程式碼執行被切換到預定義(自定義)的異常處理器函式
  • 根據情況,處理器也許會從儲存的程式碼狀態重新開始執行程式碼,終止指令碼執行,或從程式碼中另外的位置繼續執行指令碼

當異常被丟擲時,其後的程式碼不會繼續執行,PHP 會嘗試查詢匹配的 catch 程式碼塊。如果異常沒有被捕獲,而且又沒用使用 set_exception_handler() 作相應的處理的話,那麼將發生一個嚴重的錯誤(致命錯誤),並且輸出 Uncaught Exception (未捕獲異常)的錯誤訊息。

2.3 Try、throw、catch、finally

要避免上面例項中出現的錯誤,我們需要建立適當的程式碼來處理異常。

適當的處理異常程式碼應該包括:

  • Try:使用異常的函式應該位於 "try" 程式碼塊內。如果沒有觸發異常,則程式碼將照常繼續執行。但是如果異常被觸發,會丟擲一個異常。
  • Throw:這裡規定如何觸發異常。每一個 "throw" 必須對應至少一個 "catch"。
  • Catch:捕獲異常,並建立一個包含異常資訊的物件。
  • finally:用於包含無論是否發生異常都需要執行的程式碼。這通常用於清理資源,如關閉檔案控制代碼、資料庫連線或執行其他必須完成的操作

讓我們觸發一個異常:

<?php
// 建立一個有異常處理的函式
function checkNum($number)
{
    if($number>1)
    {
        throw new Exception("變數值必須小於等於 1");
    }
        return true;
}    
// 在 try 塊 觸發異常
try
{
    checkNum(2);
    // 如果丟擲異常,以下文字不會輸出
    echo '如果輸出該內容,說明 $number 變數';
}
// 捕獲異常
catch(Exception $e)
{
    echo 'Message: ' .$e->getMessage();
}
?>

上面程式碼將得到類似這樣一個錯誤:Message: 變數值必須小於等於 1

2.4 自定義異常

建立自定義的異常處理程式非常簡單。建立了一個專門的類,當 PHP 中發生異常時,可呼叫其函式。該類必須是 exception 類的一個擴充套件。

這個自定義的 customException 類繼承了 PHPexception 類的所有屬性,可向其新增自定義的函式。

<?php
class customException extends Exception
{
    public function errorMessage()
    {
        // 錯誤資訊
        $errorMsg = '錯誤行號 '.$this->getLine().' in '.$this->getFile()
        .': <b>'.$this->getMessage().'</b> 不是一個合法的 E-Mail 地址';
        return $errorMsg;
    }
}
$email = "someone@example...com"; 
try
{
    // 檢測郵箱
    if(filter_var($email, FILTER_VALIDATE_EMAIL) === FALSE)
    {
        // 如果是個不合法的郵箱地址,丟擲異常
        throw new customException($email);
    }
} 
catch (customException $e)
{
//display custom message
echo $e->errorMessage();
}
?>

上面的程式碼丟擲了一個異常,並透過一個自定義的 exception 類來捕獲它:

  • customException() 類是作為舊的 exception 類的一個擴充套件來建立的。這樣它就繼承了舊的 exception 類的所有屬性和方法。
  • 建立 errorMessage() 函式。如果 e-mail 地址不合法,則該函式返回一條錯誤訊息。
  • $email 變數設定為不合法的 e-mail 地址字串。
  • 執行 "try" 程式碼塊,由於 e-mail 地址不合法,因此丟擲一個異常。
  • "catch" 程式碼塊捕獲異常,並顯示錯誤訊息。

2.5 設定頂層異常處理器

set_exception_handler() 函式可設定處理所有未捕獲異常的使用者定義函式。

<?php
function myException($exception)
{
    echo "<b>Exception:</b> " , $exception->getMessage();
}
 
set_exception_handler('myException');
 
throw new Exception('Uncaught Exception occurred');
?>

以上程式碼的輸出如下所示:Exception: Uncaught Exception occurred
在上面的程式碼中,不存在 "catch" 程式碼塊,而是觸發頂層的異常處理程式。應該使用此函式來捕獲所有未被捕獲的異常。

3 錯誤與異常總結

3.1 區別

PHP 中,錯誤(Errors)和異常(Exceptions)是兩個不同的概念,用於處理不同型別的執行時問題。它們之間的主要區別如下:

  • 錯誤(Errors
    • 錯誤通常是更低階別的、更嚴重的執行時問題,這些問題通常是由於 PHP 引擎內部發生的問題或違反了語言規則導致的。
    • 錯誤包括如型別錯誤(Type Errors)、致命錯誤(Fatal Errors)、解析錯誤(Parse Errors)等。
    • 錯誤通常不能被捕獲(除了使用特殊的錯誤處理機制,如 register_shutdown_function() 和 error_get_last()),並且它們會終止指令碼的執行。
    • PHP 7 引入了可捕獲的錯誤(Catchable Errors),這些錯誤可以透過 Error 類進行捕獲和處理,但它們在本質上仍然是錯誤,而不是異常。
  • 異常(Exceptions)
    • 異常是程式執行過程中出現的異常情況,通常是由於程式邏輯錯誤或不可預見的條件導致的。
    • 異常可以被丟擲(throw)和捕獲(catch),允許開發者在異常發生時採取適當的措施,如記錄日誌、回滾事務或執行其他恢復操作。
    • 異常可以透過繼承 Exception 類或其子類來定義自定義的異常型別。
    • 當異常被丟擲時,程式的執行會立即停止當前的操作,並跳轉到最近的匹配的 catch 塊(如果存在)。如果沒有匹配的 catch 塊,異常將冒泡到呼叫棧的上一層,直到被捕獲或到達指令碼的頂層並導致指令碼終止。

區別

  • 嚴重性和級別:錯誤通常表示更嚴重的、更低階別的問題,而異常通常表示程式邏輯上的錯誤或異常情況。
  • 處理方式:錯誤通常不能被直接捕獲(除了可捕獲的錯誤),而異常可以透過 try/catch 塊進行捕獲和處理。
  • 中斷執行:錯誤通常會立即終止指令碼的執行,而異常可以透過捕獲來避免指令碼的終止,並允許開發者執行恢復操作。
  • 自定義性:異常可以透過繼承 Exception 類或其子類來定義自定義的異常型別,而錯誤通常不能自定義(除了使用者定義的錯誤處理器)。

相關文章