目錄
PHP 的錯誤與例外
PHP 的錯誤
PHP 對錯誤的處理
PHP 异常
PHP 异常机制
PHP 错误分类
PHP7 的错误和异常
PHP7 的 ERROR 处理
Error 和 Exception 的选择
对错误和异常的一种实践
首頁 後端開發 PHP7 討論php的錯誤和異常處理機制

討論php的錯誤和異常處理機制

Jun 22, 2020 pm 05:50 PM
php

討論php的錯誤和異常處理機制

「宣告: 本文採用 CC BY-NC-ND 4.0 授權。

原先的 PHP 只有錯誤沒有例外。看一些舊的文檔你能看到不少錯誤輸出是直接 echo html 標籤的。而現代一點的框架早已經包裝好了一切,直接拋出異常就可以有比較漂亮的錯誤顯示頁面,例如 rails 的 better errors。當然,PHP 的現代框架也已經做的不錯了,例如 laravel。然而我司目前還是用 codeigniter 2,它的錯誤和異常處理還比較簡陋。藉著升級到        PHP7 的契機梳理了一下 PHP 的錯誤和異常處理的機制。

推薦教學:《PHP教學

PHP 的錯誤與例外

PHP5 已經實作了例外的處理,這和其他語言差異不大,無非是try, catch, uncaught,按下不表,先說錯誤。

PHP 的錯誤

除了例外 PHP5 常見的就是拋出錯誤。你可以在官方文件找到所有的錯誤的定義,這些錯誤可以大致分為 WARNING, ERROR(fatal error), NOTICE 等1。 PHP的錯誤機制總結一文中給出了每種錯誤出現的場景。

E_DEPRECATED(8192) 執行階段通知,啟用後將會對在未來版本中可能無法正常運作的程式碼給予警告。

E_USER_DEPRECATED(16384) 是由使用者自己在程式碼中使用PHP函數 trigger_error() 來產生的

E_NOTICE(8) 執行時間通知。表示腳本遇到可能會表現為錯誤的情況

E_USER_NOTICE(1024) 是使用者自己在程式碼中使用PHP的trigger_error() 函數來產生的通知訊息

#E_WARNING(2) 執行時期警告(非致命錯誤)

E_USER_WARNING(512) 使用者自己在程式碼中使用PHP的trigger_error() 函數來產生的

#E_CORE_WARNING(32) PHP初始化啟動過程中由PHP引擎核心產生的警告

E_COMPILE_WARNING(128) Zend腳本引擎產生編譯時警告

E_ERROR(1) 致命的執行階段錯誤

E_USER_ERROR(256) 使用者自己在程式碼中使用PHP的trigger_error()函數來產生的

E_CORE_ERROR(16) 在PHP初始化啟動過程中由PHP引擎核心產生的致命錯誤

#E_COMPILE_ERROR(64) Zend腳本引擎產生的致命編譯時錯誤

E_PARSE(4) 編譯時語法解析錯誤。解析錯誤僅由分析器產生

E_STRICT(2048) 啟用PHP 對程式碼的修改建議,以確保程式碼具有最佳的互通性和向前相容性

#E_RECOVERABLE_ERROR(4096) 可被捕捉的致命錯誤。它表示發生了一個可能非常危險的錯誤,但還沒有導致PHP引擎處於不穩定的狀態。如果該錯誤沒有被使用者自訂句柄捕獲 (參見 set_error_handler() ),將成為一個 E_ERROR  從而腳本會終止執行。

E_ALL(30719) 所有錯誤和警告訊息(手冊上說不包含E_STRICT, 經過測試其實是包含E_STRICT的)。

常見的有:

<?php
// E_ERROR
nonexist(); // PHP Fatal error:  Call to undefined function nonexist()
throw new Exception(&#39;&#39;); // 未捕获异常也是 fatal error

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

// E_WARNNING
require &#39;nonexist.php&#39; // warning and fatal error
登入後複製
   

由於歷史原因,這個老舊的 ci2 框架有不少不合理的地方,例如會讀取不存在的 log 文件;我們對 PHP 也有一些不規範的使用,例如:

<?php
$req = [];
$user_id = $req[&#39;user_id&#39;]; // PHP error:  Undefined offset
if (null === $user_id) { /* do something */}
登入後複製
   

我們的程式碼不少地方較為依賴這種獲取不存在 key 得到 null 的表現,而每次這樣使用都是會有一個 E_NOTICE 錯誤的。雖然可以透過 array_exists 來做 if else,但畢竟比較麻煩。 PHP7 之後可以透過資料結構插件來使用 Map, Set, Vector 等明確的資料結構,從而較好的解決這個問題。

PHP 對錯誤的處理

如果沒有做任何配置,PHP 的錯誤是會直接印出來的。古老的 PHP 應用程式也確實有這麼做的。但現代應用顯然不能這樣,現代應用的錯誤應該遵循一下規則2

#一定要讓PHP 報告錯誤;

在開發環境中要顯示錯誤;

在生產環境中不能顯示錯誤;

在開發和生產環境中都要記錄錯誤。

在生產環境下,錯誤不能直接列印出來,應該記到 log 檔案中,並傳回使用者一個籠統的錯誤訊息。 set_error_handler 函數就是設定使用者自訂的錯誤處理函數,以處理腳本中出現的錯誤。我們可以在這個函數中將錯誤訊息打到 log 檔案中,並統一回傳錯誤訊息。

本来这个函数是搭配 trigger_error 函数使用的。用户通过 trigger_error 产生 error,然后用 error_handler 来处理错误。只是在这种场景下往往「异常」更好用,所以这么用的并不多。

在前述的系统自带的 16 种错误中,有一部分相当重要的错误并不能被 error_handler 捕获3

以下级别的错误不能由用户定义的函数来处理: E_ERROR、 E_PARSE、 E_CORE_ERROR、 E_CORE_WARNING、 E_COMPILE_ERROR、E_COMPILE_WARNING,和在调用 set_error_handler() 函数所在文件中产生的大多数 E_STRICT。

这些错误将无法记录下来,同时也不方便统一处理4。在 PHP7 之前的 PHP 版本一个很大的痛点就是:发生了 E_ERROR 错误,无法捕获,导致数据库的事务无法回滚造成数据不一致5

另外一个需要注意的是, error_handler 处理完毕,脚本将会继续执行发生错误的后一行。在某些情况下,你可能希望遇到某些错误可以中断脚本的执行。在官方文档中已说明,

同时注意,在需要时你有责任使用 die()。 如果错误处理程序返回了,脚本将会继续执行发生错误的后一行。

也就是说,某些情况下,我们处理完 E_WARNING 之后,需要及时退出脚本(即 die() 或者 exit())。

PHP 异常

异常是对程序错误的一种优秀的处理方式,较于错误,异常的优点是默认打印调用栈,便于调试,可控等,可以参考一下鸟哥的文章我们什么时候应该使用异常,清晰的点明了错误码和异常的优缺点。

对异常的处理也要遵循前述的错误处理规则2。在我们的日常开发中,不可能保证可以 catch 所有的异常,而未被 catch 的异常将以 fatal error 的形式中断脚本的执行并输出错误信息。所以要借助 set_exception_handler,统一处理所有未被 catch 的异常。我们可以像 error_handler 那样,在 exception_handler 中处理 log,将数据库的事务回滚。

前面提到,error_handler 需要在必要的时候手动中断脚本, PHP 文档中给出的一种实践是,在 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,都会以 Uncaught ErrorException 的形式返回并中断脚本。

PHP 异常机制

鸟哥通过一个例子讲解了 PHP 的异常的处理机制,在这里转述一下。

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

require("nonexist.php");
登入後複製
   

其运行结果为

  1. Error Occurred
  2. PHP Fatal error

而 onException 并没有执行到,说明在 error_handler 中 throw exception 不会被 exception_handler 截获。

require 不存在的文件会抛出两个错误,

  1. WARNING : 在PHP试图打开这个文件的时候抛出
  2. E_COMPILE_ERROR : 从PHP打开文件的函数返回失败以后抛出

PHP 中的异常处理机制如下:

   

而PHP在遇到 Fatal Error 的时候,会直接 zend_bailout,而 zend_bailout 会导致程序流程直接跳过上面代码段,也可以理解为直接 exit 了(longjmp),这就导致了 user_exception_handler 没有机会发生作用。

PHP 错误分类

综上所述,在 PHP 中,错误和异常可以分为以下 3 个类别:异常,可截获错误,不可截获错误。异常和可截获错误虽然机理不同,但可以当做是同一种处理方式,而不可截获错误是另一种,是一种较为棘手的错误类型。马上将会讲到,PHP7 中的 fatal error 是一种继承自 Throwable 的 Error,是可以被 try catch 住的。通过这一方式 PHP7 解决了这一难题。

PHP7 的错误和异常

PHP 7 改变了大多数错误的报告方式。不同于传统(PHP 5)的错误报告机制,现在大多数错误被作为 Error 异常抛出(在 PHP7 中,只有 fatal error 和 recoverable error 抛出异常,其他 error 比如 warning 和 notice 的表现不变6)。PHP7 中的 Error 和 Exception 的关系如图        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 类表现上和 Exception 基本一致,可以像 Exception 异常一样被第一个匹配的 try / catch 块所捕获,如果没有匹配的 catch 块,则调用异常处理函数(事先通过        set_exception_handler() 注册7)进行处理。 如果尚未注册异常处理函数,则按照传统方式处理,被报告为一个致命错误(Fatal Error)。但并非继承自 Exception 类(要考虑到和 PHP5 的兼容性),所以不能用 catch (Exception        $e) { ... } 来捕获,而需要使用 catch (Error $e) { ... },当然,也可以使用 set_exception_handler 来捕获。

但是,用户不能自己定义类实现 Throwable,这是为了保证只有 Exception 和 Error 才可以抛出。

PHP7 的 ERROR 处理

PHP7 中的 fatal error 会抛出 Error,且可以被正常 catch 到:

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

也有些错误场景下会抛出更加详细的错误,比如:

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

// ParseError
try{
  eval(&#39;i=1;&#39;);
} 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 的选择

当需要自定义处理错误的时候,应该选择继承 Error 还是 Exception 呢?

我们注意到,PHP7 中是将曾经的 fatal error 变成了 Error 抛出,而 fatal error 一般都是一些不需要在运行时处理的错误,这种错误旨在提醒程序员,这里的代码写的有问题,需要修复,而不是逻辑上要 catch 它做某些业务。

因此,绝大多数情况下,我们并不需要继承 Error,甚至 catch Error 也不常见,只在某些需要 log,回滚数据库,清理现场等场合才需要这样做。

对错误和异常的一种实践

根据以上所述,我们提炼了一个对错误和异常处理较好的实践。

  1. 对于业务中不应该出现错误的地方,抛出 InternalException,而不是 Error
<?php
class InternalException extends Exception { /*...*/ }

function find(Array $ids) {
  if (empty($ids)) {
    throw new InternalException('ids should not be empty');
  }
  ...
}
登入後複製
   
  1. 只在需要清理现场的时候 catch Error
<?php
try { /*...*/ }
catch (Throwable $t) {
  // log, transaction rollback, cleanup...
}
登入後複製
   
  1. 未捕获的 Error 和 Exception 通过 set_exception_handler 做后续清理和 log
  2. 其他错误仍然通过 set_error_handler 来处理,在处理的时候使用更加明确的 FriendlyErrorType,并抛出 ErrorException 记录调用栈

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中的错误级别与具体报错信息分类 ↩

  2. PHP 最佳实践之异常和错误 ↩ ↩2

  3. E_ERROR 无法捕获,E_RECOVERABLE_ERROR 可以,后者默认输出 Catachable fatal error ↩

  4. fatal error 会记录到 web 服务器的 error.log,这一点需要注意,因为这个 log 的位置往往不是 PHP 应用定义的,而是 web 服务器定义的。 ↩

  5. PHP 中还有一个 register_shutdown_function 函数,它允许注册一个会在 PHP 中止时执行的函数,这个函数可以捕获 fatal error,毕竟是只要是脚本中断就可以捕获的。ci2 并没有使用这个方法,所以相关问题一直没有得到很好的解决,当时也没有意识到这个函数的存在,升级 PHP7 之后可以通过                catch Error 来解决,便不再需要这样处理了。 ↩

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

  7. 在 PHP7 中,传入 exception_handler 的参数从 Exception 改为 Throwable,这意味着 exception_handler 可以截获 Error。 ↩

以上是討論php的錯誤和異常處理機制的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

<🎜>:泡泡膠模擬器無窮大 - 如何獲取和使用皇家鑰匙
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系統,解釋
3 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1664
14
CakePHP 教程
1423
52
Laravel 教程
1318
25
PHP教程
1269
29
C# 教程
1248
24
在PHP API中說明JSON Web令牌(JWT)及其用例。 在PHP API中說明JSON Web令牌(JWT)及其用例。 Apr 05, 2025 am 12:04 AM

JWT是一種基於JSON的開放標準,用於在各方之間安全地傳輸信息,主要用於身份驗證和信息交換。 1.JWT由Header、Payload和Signature三部分組成。 2.JWT的工作原理包括生成JWT、驗證JWT和解析Payload三個步驟。 3.在PHP中使用JWT進行身份驗證時,可以生成和驗證JWT,並在高級用法中包含用戶角色和權限信息。 4.常見錯誤包括簽名驗證失敗、令牌過期和Payload過大,調試技巧包括使用調試工具和日誌記錄。 5.性能優化和最佳實踐包括使用合適的簽名算法、合理設置有效期、

php程序在字符串中計數元音 php程序在字符串中計數元音 Feb 07, 2025 pm 12:12 PM

字符串是由字符組成的序列,包括字母、數字和符號。本教程將學習如何使用不同的方法在PHP中計算給定字符串中元音的數量。英語中的元音是a、e、i、o、u,它們可以是大寫或小寫。 什麼是元音? 元音是代表特定語音的字母字符。英語中共有五個元音,包括大寫和小寫: a, e, i, o, u 示例 1 輸入:字符串 = "Tutorialspoint" 輸出:6 解釋 字符串 "Tutorialspoint" 中的元音是 u、o、i、a、o、i。總共有 6 個元

解釋PHP中的晚期靜態綁定(靜態::)。 解釋PHP中的晚期靜態綁定(靜態::)。 Apr 03, 2025 am 12:04 AM

靜態綁定(static::)在PHP中實現晚期靜態綁定(LSB),允許在靜態上下文中引用調用類而非定義類。 1)解析過程在運行時進行,2)在繼承關係中向上查找調用類,3)可能帶來性能開銷。

什麼是PHP魔術方法(__ -construct,__destruct,__call,__get,__ set等)並提供用例? 什麼是PHP魔術方法(__ -construct,__destruct,__call,__get,__ set等)並提供用例? Apr 03, 2025 am 12:03 AM

PHP的魔法方法有哪些? PHP的魔法方法包括:1.\_\_construct,用於初始化對象;2.\_\_destruct,用於清理資源;3.\_\_call,處理不存在的方法調用;4.\_\_get,實現動態屬性訪問;5.\_\_set,實現動態屬性設置。這些方法在特定情況下自動調用,提升代碼的靈活性和效率。

PHP和Python:比較兩種流行的編程語言 PHP和Python:比較兩種流行的編程語言 Apr 14, 2025 am 12:13 AM

PHP和Python各有優勢,選擇依據項目需求。 1.PHP適合web開發,尤其快速開發和維護網站。 2.Python適用於數據科學、機器學習和人工智能,語法簡潔,適合初學者。

PHP行動:現實世界中的示例和應用程序 PHP行動:現實世界中的示例和應用程序 Apr 14, 2025 am 12:19 AM

PHP在電子商務、內容管理系統和API開發中廣泛應用。 1)電子商務:用於購物車功能和支付處理。 2)內容管理系統:用於動態內容生成和用戶管理。 3)API開發:用於RESTfulAPI開發和API安全性。通過性能優化和最佳實踐,PHP應用的效率和可維護性得以提升。

PHP:網絡開發的關鍵語言 PHP:網絡開發的關鍵語言 Apr 13, 2025 am 12:08 AM

PHP是一種廣泛應用於服務器端的腳本語言,特別適合web開發。 1.PHP可以嵌入HTML,處理HTTP請求和響應,支持多種數據庫。 2.PHP用於生成動態網頁內容,處理表單數據,訪問數據庫等,具有強大的社區支持和開源資源。 3.PHP是解釋型語言,執行過程包括詞法分析、語法分析、編譯和執行。 4.PHP可以與MySQL結合用於用戶註冊系統等高級應用。 5.調試PHP時,可使用error_reporting()和var_dump()等函數。 6.優化PHP代碼可通過緩存機制、優化數據庫查詢和使用內置函數。 7

PHP的持久相關性:它還活著嗎? PHP的持久相關性:它還活著嗎? Apr 14, 2025 am 12:12 AM

PHP仍然具有活力,其在現代編程領域中依然佔據重要地位。 1)PHP的簡單易學和強大社區支持使其在Web開發中廣泛應用;2)其靈活性和穩定性使其在處理Web表單、數據庫操作和文件處理等方面表現出色;3)PHP不斷進化和優化,適用於初學者和經驗豐富的開發者。

See all articles