天天看點

php抛出和捕獲異常,關于php:捕獲和重新抛出異常的最佳實踐是什麼?

應該将捕獲的異常直接重新抛出,還是将它們包裝在新的異常周圍?

也就是說,我應該這樣做:

try {

$connect = new CONNECT($db, $user, $password, $driver, $host);

} catch (Exception $e) {

throw $e;

}

或這個:

try {

$connect = new CONNECT($db, $user, $password, $driver, $host);

} catch (Exception $e) {

throw new Exception("Exception Message", 1, $e);

}

如果您的答案是直接抛出,請建議使用異常連結,但我無法了解我們使用異常連結的實際情況。

除非您打算做一些有意義的事情,否則不要捕獲異常。

"有意義的事情"可能是其中之一:

處理異常

最明顯的有意義的動作是處理異常,例如通過顯示錯誤消息并中止操作:

try {

$connect = new CONNECT($db, $user, $password, $driver, $host);

}

catch (Exception $e) {

echo"Error while connecting to database!";

die;

}

記錄或部厘清除

有時,您不知道如何在特定上下文中正确處理異常。也許您缺少有關"全局"的資訊,但是您确實希望将故障記錄在盡可能接近發生故障的位置。在這種情況下,您可能想要捕獲,記錄并重新抛出:

try {

$connect = new CONNECT($db, $user, $password, $driver, $host);

}

catch (Exception $e) {

logException($e); // does something

throw $e;

}

一個相關的場景是,您可以在正确的位置對失敗的操作執行一些清理,但又不能決定應該如何在頂級處理失敗。在早期的PHP版本中,這将實作為

$connect = new CONNECT($db, $user, $password, $driver, $host);

try {

$connect->insertSomeRecord();

}

catch (Exception $e) {

$connect->disconnect(); // we don't want to keep the connection open anymore

throw $e; // but we also don't know how to respond to the failure

}

PHP 5.5引入了finally關鍵字,是以對于清理方案,現在有另一種方法可以解決此問題。如果清理代碼無論發生了什麼(無論是錯誤還是成功)都需要運作,則現在可以在透明地允許任何抛出的異常傳播的情況下執行此操作:

$connect = new CONNECT($db, $user, $password, $driver, $host);

try {

$connect->insertSomeRecord();

}

finally {

$connect->disconnect(); // no matter what

}

錯誤抽象(帶有異常連結)

第三種情況是您希望在邏輯上将許多可能的故障歸為一個更大的範圍。邏輯分組的示例:

class ComponentInitException extends Exception {

// public constructors etc as in Exception

}

class Component {

public function __construct() {

try {

$connect = new CONNECT($db, $user, $password, $driver, $host);

}

catch (Exception $e) {

throw new ComponentInitException($e->getMessage(), $e->getCode(), $e);

}

}

}

在這種情況下,您不希望Component的使用者知道它是使用資料庫連接配接實作的(也許您想保持選擇狀态并在将來使用基于檔案的存儲)。是以,您對Component的規範将說:"在初始化失敗的情況下,将抛出ComponentInitException"。這使Component的使用者可以捕獲預期類型的??異常,同時還允許調試代碼通路所有(與實作有關的)詳細資訊。

提供更豐富的上下文(帶有異常連結)

最後,在某些情況下,您可能希望為該異常提供更多上下文。在這種情況下,将異常包裝在另一個異常中是有意義的,該異常包含有關發生錯誤時您要執行的操作的更多資訊。例如:

class FileOperation {

public static function copyFiles() {

try {

$copier = new FileCopier(); // the constructor may throw

// this may throw if the files do no not exist

$copier->ensureSourceFilesExist();

// this may throw if the directory cannot be created

$copier->createTargetDirectory();

// this may throw if copying a file fails

$copier->performCopy();

}

catch (Exception $e) {

throw new Exception("Could not perform copy operation.", 0, $e);

}

}

}

這種情況與上面的情況類似(示例可能不是最好的情況),但是它說明了提供更多上下文的意義:如果引發異常,則表明檔案複制失敗。但是為什麼失敗了?包裝的異常中提供了此資訊(如果示例複雜得多,則異常可以存在多個級别)。

如果您考慮例如以下情況,則說明這樣做的價值。建立UserProfile對象将導緻檔案被複制,因為使用者配置檔案存儲在檔案中并且支援事務語義:您可以"撤消"更改,因為它們僅在配置檔案的副本上執行,直到您送出為止。

在這種情況下,如果您做了

try {

$profile = UserProfile::getInstance();

}

并導緻捕獲到"無法建立目标目錄"異常錯誤,您将有權利混淆。将此"核心"異常包裝在提供上下文的其他異常層中,将使錯誤更易于處理("建立配置檔案複制失敗"->"檔案複制操作失敗"->"無法建立目标目錄")。

阿門。忘記我的答案,請閱讀Jons。

我僅同意最後兩個原因:1 /處理異常:您不應該在此級别執行此操作; 2 /日志記錄或清理:最後使用并将異常記錄在資料層上方

@remi:除了PHP不支援finally構造(至少現在還不支援)...就這樣了,這意味着我們必須訴諸于這種肮髒的事情...

@remibourgarel:1:這隻是一個例子。當然,您不應該在此級别上這樣做,但是答案足夠長了。 2:正如@ircmaxell所說,PHP中沒有finally。

@ircmaxell:對不起,我沒有使用php那樣的代碼,是以我做了一些研究,但是看來我的來源是錯誤的bugs.php.net/bug.php?id=32100

終于,PHP 5.5現在終于實作了。

不管是否引發異常,都會執行finally塊中的代碼。請在讀取something went wrong, take corrective measures, let exception propagate的finally示例中更改您的注釋,以避免混淆。

@wadim:非常令人誤解,謝謝。我收拾了

我認為您沒有從這裡的清單中遺漏是有原因的-在您捕獲到異常并有機會對其進行檢查之前,您可能無法确定是否可以處理異常。例如,使用錯誤代碼(并且有不計其數的錯誤代碼)的低級API的包裝器可能隻有一個異常類,該異常類會抛出任何錯誤的執行個體,并帶有一個error_code屬性,該屬性可以進行檢查以擷取基本錯誤代碼。如果您僅能夠有意義地處理其中一些錯誤,則您可能想捕獲,檢查,如果無法處理錯誤,請重新抛出。

@MarkAmery:在實踐中這可能隻是個小衆市場,但這絕對是一種可能性-它也可能适用于設計不理想的異常抛出API。我不認為它在清單中有自己的位置(我打算對标準技術進行概述),但是要牢記這一點。感謝您的回報意見!

@JulienPalard:這是我第一次聽說此特定錯誤(我知道Exception構造函數通常很挑剔)。 您能否提供一個示例,其中傳入"42"而不是42是一個問題?

@Jon PHP 5.4.38此處用于記錄,$ php -r throw new Exception("42","42");給出了PHP Fatal error: Uncaught exception Exception with message 42 in Command line code:1嗡嗡聲,多數群眾贊成在正常情況下...試圖重制另一種方式...

@Jon $ php -r throw new Exception("message","code"); PHP Fatal error: Wrong parameters for Exception([string $exception [, long $code [, Exception $previous = NULL]]]) in Command line code on line 1。 有時在PDOException中發生這種情況,getCode()可以傳回包含字母的内容。 通常在MySQL SELECT * FROM unexisting_table上給出42S02。

@Jon是以,答案是否定的,我顯然無法提供一個示例,其中傳遞"42"而不是42是一個問題。

我會趕上Throwable。 Exception和Error都至少從PHP 7中的Throwable繼承。

好吧,這都是關于維護抽象的。是以,我建議使用異常連結直接抛出異常。至于為什麼,讓我解釋一下洩漏抽象的概念

假設您正在建立模型。該模型應該從應用程式的其餘部分中抽象出所有資料持久性和驗證。那麼,當您遇到資料庫錯誤時會發生什麼呢?如果重新抛出DatabaseQueryException,則表示洩漏了抽象。要了解原因,請考慮一下抽象。您不必關心模型如何存儲資料,就可以了。同樣,您不必在乎模型的基礎系統到底出了什麼問題,隻是您知道出了什麼問題,并且大概知道出了什麼問題。

是以,通過抛出DatabaseQueryException,您将洩漏抽象,并要求調用代碼了解模型下正在發生的事情的語義。而是建立一個通用ModelStorageException,并将捕獲的DatabaseQueryException包裝在其中。這樣,您的調用代碼仍然可以嘗試從語義上處理錯誤,但是,該模型的基礎技術無關緊要,因為您僅從該抽象層公開了錯誤。更好的是,由于您包裝了異常,如果異常一直冒出來并需要記錄,則可以跟蹤引發的根異常(周遊整個鍊),是以您仍然擁有所需的所有調試資訊!

除非您需要進行一些後處理,否則不要簡單地捕獲并抛出相同的異常。但是像} catch (Exception $e) { throw $e; }這樣的塊是沒有意義的。但是您可以重新包裝異常以擷取一些明顯的抽象收益。

好答案。似乎相當多的人圍繞Stack Overflow(基于答案等)使用錯誤的代碼。

恕我直言,捕獲異常以将其重新抛出是沒有用的。在這種情況下,隻是不要捕獲它,而是讓先前調用的方法對其進行處理(也就是在調用堆棧中位于"上層"的方法)。

如果将其抛出,将捕獲到的異常連結到将要抛出的新異常中絕對是一個好習慣,因為它将保留捕獲到的異常所包含的資訊。但是,僅當您添加一些資訊或處理所捕獲的異常時,重新抛出它才有用,可能是某些上下文,值,日志記錄,釋放資源等。

添加一些資訊的一種方法是擴充Exception類,使其具有諸如NullParameterException,DatabaseException等的異常。此外,這使開發人員隻能捕獲他可以處理的某些異常。例如,隻能捕獲DatabaseException并嘗試解決導緻Exception的原因,例如重新連接配接到資料庫。

它不是沒有用的,有時您需要對抛出異常的函數執行某些操作,然後将其重新抛出以使更進階别的捕獲執行其他操作。在我正在從事的一個項目中,有時我們會在操作方法中捕獲異常,向使用者顯示友好通知,然後将其重新抛出,是以代碼中更遠的try catch塊可以再次捕獲該錯誤以将錯誤記錄到一個日志。

是以,正如我所說,您向異常添加了一些資訊(顯示通知,記錄日志)。您不會像在OPs示例中那樣将其重新抛出。

好吧,如果您需要關閉資源,但是沒有其他資訊要添加,則可以将其重新抛出。我同意這不是世界上最幹淨的東西,但它并不可怕

@ircmaxell同意,已進行編輯以反映出僅當您除了将其扔掉以外不做任何其他事情時它才是無用的

重要的一點是,通過重新抛出該異常,您可以松開該檔案和/或行的資訊,以了解最初引發異常的位置。是以,通常最好先提出一個新的問題,然後再傳遞一個舊的問題,就像問題的第二個例子一樣。否則,它隻會指向catch塊,讓您猜測實際的問題是什麼。

我在這裡了解如何處理這種"無用"的情況。送出請求時發生任何錯誤時,Guzzle都會引發通用異常。可以處理諸如OAuth令牌重新整理之類的問題。但是要确定OAuth令牌是否已過期,需要對響應進行更深入的檢查。如果發現不是這種情況,則需要重新抛出異常,以便更進一步的處理任何異常。異常可能太廣泛了,您不知道是否可以處理它,直到更深入。

您必須了解PHP 5.3中的Exception Best Practices

任何情況下,PHP中的異常處理都不是新功能。在以下連結中,您将看到PHP 5.3中基于異常的兩個新功能。第一個是嵌套異常,第二個是由SPL擴充(現在是PHP運作時的核心擴充)提供的一組新的異常類型。這兩個新功能均已進入最佳實踐手冊,應進行詳細檢查。

http://ralphschindler.com/2010/09/15/exception-best-practices-in-php-5-3

您通常是這樣想的。

一個類可能會抛出許多不比對的異常。是以,您可以為該類或類的類型建立一個異常類并将其抛出。

是以,使用該類的代碼僅必須捕獲一種類型的異常。

嘿,您能否提供更多詳細資訊或連結,以便在其中閱讀有關此方法的更多資訊。