溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

php中的錯(cuò)誤處理與異常處理機(jī)制介紹

發(fā)布時(shí)間:2020-06-22 20:44:46 來(lái)源:億速云 閱讀:193 作者:Leah 欄目:編程語(yǔ)言

這篇文章將為大家詳細(xì)講解有關(guān)php中的錯(cuò)誤處理與異常處理機(jī)制,文章內(nèi)容質(zhì)量較高,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。

PHP 的錯(cuò)誤和異常

PHP5 已經(jīng)實(shí)現(xiàn)了異常的處理,這和其他語(yǔ)言差別不大,無(wú)非就是 try, catch, uncaught,按下不表,先說錯(cuò)誤。

PHP 的錯(cuò)誤

除了異常 PHP5 常見的就是拋出錯(cuò)誤。你可以在官方文檔找到所有的錯(cuò)誤的定義,這些錯(cuò)誤可以大致分為 WARNING, ERROR(fatal error), NOTICE 等1。PHP的錯(cuò)誤機(jī)制總結(jié)一文中給出了每種錯(cuò)誤出現(xiàn)的場(chǎng)景。

E_DEPRECATED(8192) 運(yùn)行時(shí)通知,啟用后將會(huì)對(duì)在未來(lái)版本中可能無(wú)法正常工作的代碼給出警告。

E_USER_DEPRECATED(16384) 是由用戶自己在代碼中使用PHP函數(shù) trigger_error() 來(lái)產(chǎn)生的

E_NOTICE(8) 運(yùn)行時(shí)通知。表示腳本遇到可能會(huì)表現(xiàn)為錯(cuò)誤的情況

E_USER_NOTICE(1024) 是用戶自己在代碼中使用PHP的trigger_error() 函數(shù)來(lái)產(chǎn)生的通知信息

E_WARNING(2) 運(yùn)行時(shí)警告 (非致命錯(cuò)誤)

E_USER_WARNING(512) 用戶自己在代碼中使用PHP的 trigger_error() 函數(shù)來(lái)產(chǎn)生的

E_CORE_WARNING(32) PHP初始化啟動(dòng)過程中由PHP引擎核心產(chǎn)生的警告

E_COMPILE_WARNING(128) Zend腳本引擎產(chǎn)生編譯時(shí)警告

E_ERROR(1) 致命的運(yùn)行時(shí)錯(cuò)誤

E_USER_ERROR(256) 用戶自己在代碼中使用PHP的 trigger_error()函數(shù)來(lái)產(chǎn)生的

E_CORE_ERROR(16) 在PHP初始化啟動(dòng)過程中由PHP引擎核心產(chǎn)生的致命錯(cuò)誤

E_COMPILE_ERROR(64) Zend腳本引擎產(chǎn)生的致命編譯時(shí)錯(cuò)誤

E_PARSE(4) 編譯時(shí)語(yǔ)法解析錯(cuò)誤。解析錯(cuò)誤僅僅由分析器產(chǎn)生

E_STRICT(2048) 啟用 PHP 對(duì)代碼的修改建議,以確保代碼具有最佳的互操作性和向前兼容性

E_RECOVERABLE_ERROR(4096) 可被捕捉的致命錯(cuò)誤。 它表示發(fā)生了一個(gè)可能非常危險(xiǎn)的錯(cuò)誤,但是還沒有導(dǎo)致PHP引擎處于不穩(wěn)定的狀態(tài)。 如果該錯(cuò)誤沒有被用戶自定義句柄捕獲 (參見 set_error_handler() ),將成為一個(gè) E_ERROR  從而腳本會(huì)終止運(yùn)行。

E_ALL(30719) 所有錯(cuò)誤和警告信息(手冊(cè)上說不包含E_STRICT, 經(jīng)過測(cè)試其實(shí)是包含E_STRICT的)。

常見的有:

<?php
// E_ERROR
nonexist(); // PHP Fatal error:  Call to undefined function nonexist()
throw new Exception(''); // 未捕獲異常也是 fatal error

// E_NOTICE
$a = $b; //  PHP Notice:  Undefined variable
$a = []; $a[2]; // PHP Notice:  Undefined offset: 2

// E_WARNNING
require 'nonexist.php' // warning and fatal error

由于歷史原因,這個(gè)老舊的 ci2 框架有不少不合理的地方,比如會(huì)讀取不存在的 log 文件;我們對(duì) PHP 也有一些不規(guī)范的使用,比如:

<?php
$req = [];
$user_id = $req['user_id']; // PHP error:  Undefined offset
if (null === $user_id) { /* do something */}

我們的代碼不少地方較為依賴這種獲取不存在 key 得到 null 的表現(xiàn),而每次這樣使用都是會(huì)有一個(gè) E_NOTICE 錯(cuò)誤的。雖然可以通過 array_exists 來(lái)做 if else,但畢竟比較麻煩。PHP7 之后可以通過數(shù)據(jù)結(jié)構(gòu)插件來(lái)使用 Map, Set, Vector 等明確的數(shù)據(jù)結(jié)構(gòu),從而較好的解決這個(gè)問題。

PHP 對(duì)錯(cuò)誤的處理

如果沒有做任何配置,PHP 的錯(cuò)誤是會(huì)直接打印出來(lái)的。古老的 PHP 應(yīng)用也確實(shí)有這么做的。但現(xiàn)代應(yīng)用顯然不能這樣,現(xiàn)代應(yīng)用的錯(cuò)誤應(yīng)該遵循一下規(guī)則2

一定要讓 PHP 報(bào)告錯(cuò)誤;

在開發(fā)環(huán)境中要顯示錯(cuò)誤;

在生產(chǎn)環(huán)境中不能顯示錯(cuò)誤;

在開發(fā)和生產(chǎn)環(huán)境中都要記錄錯(cuò)誤。

在生產(chǎn)環(huán)境下,錯(cuò)誤不能直接打印出來(lái),應(yīng)該記到 log 文件中,并返回用戶一個(gè)籠統(tǒng)的錯(cuò)誤信息。set_error_handler 函數(shù)就是設(shè)置用戶自定義的錯(cuò)誤處理函數(shù),以處理腳本中出現(xiàn)的錯(cuò)誤。我們可以在這個(gè)函數(shù)中將錯(cuò)誤信息打到 log 文件中,并統(tǒng)一返回錯(cuò)誤信息。

本來(lái)這個(gè)函數(shù)是搭配 trigger_error 函數(shù)使用的。用戶通過 trigger_error 產(chǎn)生 error,然后用 error_handler 來(lái)處理錯(cuò)誤。只是在這種場(chǎng)景下往往「異?!垢糜?,所以這么用的并不多。

在前述的系統(tǒng)自帶的 16 種錯(cuò)誤中,有一部分相當(dāng)重要的錯(cuò)誤并不能被 error_handler 捕獲3

以下級(jí)別的錯(cuò)誤不能由用戶定義的函數(shù)來(lái)處理: E_ERROR、 E_PARSE、 E_CORE_ERROR、 E_CORE_WARNING、 E_COMPILE_ERROR、E_COMPILE_WARNING,和在調(diào)用 set_error_handler() 函數(shù)所在文件中產(chǎn)生的大多數(shù) E_STRICT。

這些錯(cuò)誤將無(wú)法記錄下來(lái),同時(shí)也不方便統(tǒng)一處理4。在 PHP7 之前的 PHP 版本一個(gè)很大的痛點(diǎn)就是:發(fā)生了 E_ERROR 錯(cuò)誤,無(wú)法捕獲,導(dǎo)致數(shù)據(jù)庫(kù)的事務(wù)無(wú)法回滾造成數(shù)據(jù)不一致5。

另外一個(gè)需要注意的是, error_handler 處理完畢,腳本將會(huì)繼續(xù)執(zhí)行發(fā)生錯(cuò)誤的后一行。在某些情況下,你可能希望遇到某些錯(cuò)誤可以中斷腳本的執(zhí)行。在官方文檔中已說明,

同時(shí)注意,在需要時(shí)你有責(zé)任使用 die()。 如果錯(cuò)誤處理程序返回了,腳本將會(huì)繼續(xù)執(zhí)行發(fā)生錯(cuò)誤的后一行。

也就是說,某些情況下,我們處理完 E_WARNING 之后,需要及時(shí)退出腳本(即 die() 或者 exit())。

PHP 異常

異常是對(duì)程序錯(cuò)誤的一種優(yōu)秀的處理方式,較于錯(cuò)誤,異常的優(yōu)點(diǎn)是默認(rèn)打印調(diào)用棧,便于調(diào)試,可控等,可以參考一下鳥哥的文章我們什么時(shí)候應(yīng)該使用異常,清晰的點(diǎn)明了錯(cuò)誤碼和異常的優(yōu)缺點(diǎn)。

對(duì)異常的處理也要遵循前述的錯(cuò)誤處理規(guī)則2。在我們的日常開發(fā)中,不可能保證可以 catch 所有的異常,而未被 catch 的異常將以 fatal error 的形式中斷腳本的執(zhí)行并輸出錯(cuò)誤信息。所以要借助 set_exception_handler,統(tǒng)一處理所有未被        catch 的異常。我們可以像 error_handler 那樣,在 exception_handler 中處理 log,將數(shù)據(jù)庫(kù)的事務(wù)回滾。

前面提到,error_handler 需要在必要的時(shí)候手動(dòng)中斷腳本, PHP 文檔中給出的一種實(shí)踐是,在 error_handler 中 throw ErrorException,代碼示例如下:

<?php
function exception_error_handler($severity, $message, $file, $line) {
    if (!(error_reporting() & $severity)) {
        // This error code is not included in error_reporting
        return;
    }
    throw new ErrorException($message, 0, $severity, $file, $line);
}
set_error_handler("exception_error_handler");

/* Trigger exception */
strpos();

這樣凡是不想忽略的 error,都會(huì)以 Uncaught ErrorException 的形式返回并中斷腳本。

PHP 異常機(jī)制

鳥哥通過一個(gè)例子講解了 PHP 的異常的處理機(jī)制,在這里轉(zhuǎn)述一下。

<?php
function onError($errCode, $errMesg, $errFile, $errLine) {
    echo "Error Occurred\n";
    throw new Exception($errMesg);
}
 
function onException($e) {
    echo '********exception: ' . $e->getMessage();
}
 
set_error_handler("onError");
 
set_exception_handler("onException");

require("nonexist.php");

其運(yùn)行結(jié)果為

  1. Error Occurred
  2. PHP Fatal error

而 onException 并沒有執(zhí)行到,說明在 error_handler 中 throw exception 不會(huì)被 exception_handler 截獲。

require 不存在的文件會(huì)拋出兩個(gè)錯(cuò)誤,

  1. WARNING : 在PHP試圖打開這個(gè)文件的時(shí)候拋出
  2. E_COMPILE_ERROR : 從PHP打開文件的函數(shù)返回失敗以后拋出

PHP 中的異常處理機(jī)制如下:

php中的錯(cuò)誤處理與異常處理機(jī)制介紹    

而PHP在遇到 Fatal Error 的時(shí)候,會(huì)直接 zend_bailout,而 zend_bailout 會(huì)導(dǎo)致程序流程直接跳過上面代碼段,也可以理解為直接 exit 了(longjmp),這就導(dǎo)致了 user_exception_handler 沒有機(jī)會(huì)發(fā)生作用。

PHP 錯(cuò)誤分類

綜上所述,在 PHP 中,錯(cuò)誤和異??梢苑譃橐韵?3 個(gè)類別:異常,可截獲錯(cuò)誤,不可截獲錯(cuò)誤。異常和可截獲錯(cuò)誤雖然機(jī)理不同,但可以當(dāng)做是同一種處理方式,而不可截獲錯(cuò)誤是另一種,是一種較為棘手的錯(cuò)誤類型。馬上將會(huì)講到,PHP7 中的 fatal error 是一種繼承自 Throwable 的 Error,是可以被 try catch 住的。通過這一方式 PHP7 解決了這一難題。

PHP7 的錯(cuò)誤和異常

PHP 7 改變了大多數(shù)錯(cuò)誤的報(bào)告方式。不同于傳統(tǒng)(PHP 5)的錯(cuò)誤報(bào)告機(jī)制,現(xiàn)在大多數(shù)錯(cuò)誤被作為 Error 異常拋出(在 PHP7 中,只有 fatal error 和 recoverable error 拋出異常,其他 error 比如 warning 和 notice 的表現(xiàn)不變6)。PHP7 中的 Error 和 Exception 的關(guān)系如圖        6

interface Throwable
    |- Exception implements Throwable
        |- ...
    |- Error implements Throwable
        |- TypeError extends Error
        |- ParseError extends Error
        |- ArithmeticError extends Error
            |- pisionByZeroError extends ArithmeticError
        |- AssertionError extends Error

值得注意的是,Error 類表現(xiàn)上和 Exception 基本一致,可以像 Exception 異常一樣被第一個(gè)匹配的 try / catch 塊所捕獲,如果沒有匹配的 catch 塊,則調(diào)用異常處理函數(shù)(事先通過        set_exception_handler() 注冊(cè)7)進(jìn)行處理。 如果尚未注冊(cè)異常處理函數(shù),則按照傳統(tǒng)方式處理,被報(bào)告為一個(gè)致命錯(cuò)誤(Fatal Error)。但并非繼承自 Exception 類(要考慮到和 PHP5 的兼容性),所以不能用 catch (Exception        $e) { ... } 來(lái)捕獲,而需要使用 catch (Error $e) { ... },當(dāng)然,也可以使用 set_exception_handler 來(lái)捕獲。

但是,用戶不能自己定義類實(shí)現(xiàn) Throwable,這是為了保證只有 Exception 和 Error 才可以拋出。

PHP7 的 ERROR 處理

PHP7 中的 fatal error 會(huì)拋出 Error,且可以被正常 catch 到:

<?php
$a = 1;
try {
  $a->nonexist();
} catch (Error $e) {
  // Handle error
}

也有些錯(cuò)誤場(chǎng)景下會(huì)拋出更加詳細(xì)的錯(cuò)誤,比如:

<?php
// TypeError
function test(int $i) {
  echo $i;
}
try {
  test('test');
} catch (TypeError $e) {
  // Handle error
}

// ParseError
try{
  eval('i=1;');
} catch (ParseError $e) { 
  echo $e->getMessage(), "\n";
}

// ArithmeticError
try {
    $value = 1 << -1;
} catch (ArithmeticError $e) {
    echo $e->getMessage(), "\n";
}

// pisionByZeroError
try {
    $value = 1 % 0;
} catch (pisionByZeroError $e) {
    echo $e->getMessage(), "\n";
}

Error 和 Exception 的選擇

當(dāng)需要自定義處理錯(cuò)誤的時(shí)候,應(yīng)該選擇繼承 Error 還是 Exception 呢?

我們注意到,PHP7 中是將曾經(jīng)的 fatal error 變成了 Error 拋出,而 fatal error 一般都是一些不需要在運(yùn)行時(shí)處理的錯(cuò)誤,這種錯(cuò)誤旨在提醒程序員,這里的代碼寫的有問題,需要修復(fù),而不是邏輯上要 catch 它做某些業(yè)務(wù)。

因此,絕大多數(shù)情況下,我們并不需要繼承 Error,甚至 catch Error 也不常見,只在某些需要 log,回滾數(shù)據(jù)庫(kù),清理現(xiàn)場(chǎng)等場(chǎng)合才需要這樣做。

對(duì)錯(cuò)誤和異常的一種實(shí)踐

根據(jù)以上所述,我們提煉了一個(gè)對(duì)錯(cuò)誤和異常處理較好的實(shí)踐。

  1. 對(duì)于業(yè)務(wù)中不應(yīng)該出現(xiàn)錯(cuò)誤的地方,拋出 InternalException,而不是 Error
<?php
class InternalException extends Exception { /*...*/ }

function find(Array $ids) {
  if (empty($ids)) {
    throw new InternalException('ids should not be empty');
  }
  ...
}
  1. 只在需要清理現(xiàn)場(chǎng)的時(shí)候 catch Error
<?php
try { /*...*/ }
catch (Throwable $t) {
  // log, transaction rollback, cleanup...
}
  1. 未捕獲的 Error 和 Exception 通過 set_exception_handler 做后續(xù)清理和 log
  2. 其他錯(cuò)誤仍然通過 set_error_handler 來(lái)處理,在處理的時(shí)候使用更加明確的 FriendlyErrorType,并拋出 ErrorException 記錄調(diào)用棧

FriendlyErrorType:

<?php
function FriendlyErrorType($type) 
{ 
    switch($type) 
    { 
        case E_ERROR: // 1 // 
            return 'E_ERROR'; 
        case E_WARNING: // 2 // 
            return 'E_WARNING'; 
        case E_PARSE: // 4 // 
            return 'E_PARSE'; 
        case E_NOTICE: // 8 // 
            return 'E_NOTICE'; 
        case E_CORE_ERROR: // 16 // 
            return 'E_CORE_ERROR'; 
        case E_CORE_WARNING: // 32 // 
            return 'E_CORE_WARNING'; 
        case E_COMPILE_ERROR: // 64 // 
            return 'E_COMPILE_ERROR'; 
        case E_COMPILE_WARNING: // 128 // 
            return 'E_COMPILE_WARNING'; 
        case E_USER_ERROR: // 256 // 
            return 'E_USER_ERROR'; 
        case E_USER_WARNING: // 512 // 
            return 'E_USER_WARNING'; 
        case E_USER_NOTICE: // 1024 // 
            return 'E_USER_NOTICE'; 
        case E_STRICT: // 2048 // 
            return 'E_STRICT'; 
        case E_RECOVERABLE_ERROR: // 4096 // 
            return 'E_RECOVERABLE_ERROR'; 
        case E_DEPRECATED: // 8192 // 
            return 'E_DEPRECATED'; 
        case E_USER_DEPRECATED: // 16384 // 
            return 'E_USER_DEPRECATED'; 
    } 
    return ""; 
}

error_handler:

<?php
function exception_error_handler($severity, $message, $file, $line) {
    if (!(error_reporting() & $severity)) {
        // This error code is not included in error_reporting
        return;
    }
 	log FriendlyErrorType($severity);
    throw new ErrorException($message, 0, $severity, $file, $line);
}
set_error_handler("exception_error_handler");
  1. PHP中的錯(cuò)誤級(jí)別與具體報(bào)錯(cuò)信息分類 ?

  2. PHP 最佳實(shí)踐之異常和錯(cuò)誤 ? ?2

  3. E_ERROR 無(wú)法捕獲,E_RECOVERABLE_ERROR 可以,后者默認(rèn)輸出 Catachable fatal error ?

  4. fatal error 會(huì)記錄到 web 服務(wù)器的 error.log,這一點(diǎn)需要注意,因?yàn)檫@個(gè) log 的位置往往不是 PHP 應(yīng)用定義的,而是 web 服務(wù)器定義的。 ?

  5. PHP 中還有一個(gè) register_shutdown_function 函數(shù),它允許注冊(cè)一個(gè)會(huì)在 PHP 中止時(shí)執(zhí)行的函數(shù),這個(gè)函數(shù)可以捕獲 fatal error,畢竟是只要是腳本中斷就可以捕獲的。ci2 并沒有使用這個(gè)方法,所以相關(guān)問題一直沒有得到很好的解決,當(dāng)時(shí)也沒有意識(shí)到這個(gè)函數(shù)的存在,升級(jí) PHP7 之后可以通過                catch Error 來(lái)解決,便不再需要這樣處理了。 ?

  6. Throwable Exceptions and Errors in PHP 7 ? ?2

  7. 在 PHP7 中,傳入 exception_handler 的參數(shù)從 Exception 改為 Throwable,這意味著 exception_handler 可以截獲 Error。 ?

以上就是php中的錯(cuò)誤處理與異常處理機(jī)制介紹,看完之后是否有所收獲呢?如果想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊,感謝各位的閱讀。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI