catch用法 - php exception message



在PHP5中,我應該使用Exceptions還是trigger_error/set_error_handler? (6)

做任何一種方式的優點/缺點是什麼? 有一條正確的方法(tm)?

https://ffff65535.com


介紹

根據我的個人經驗,作為一般規則,我更喜歡在我的代碼中使用Exceptions而不是trigger_error。 這主要是因為使用Exceptions比觸發錯誤更靈活。 而且,恕我直言,這不僅對我自己和第三方開發者也有好處。

  1. 我可以擴展Exception類(或使用異常代碼)來明確區分我的庫的狀態。 這有助於我和第三方開發人員處理和調試代碼。 這也揭示了它可以在不需要源代碼瀏覽的情況下失敗的地方和原因
  2. 我可以有效地暫停我的庫的執行而不會停止腳本的執行。
  3. 第三方開發人員可以鏈接我的異常(在PHP> 5.3。*中)對於調試非常有用,並且可能在處理由於不同原因導致我的庫失敗的情況時很方便。

我可以做到這一切,而不會強加他應該如何處理我的庫故障。 (即:創建複雜的錯誤處理函數)。 他可以使用try catch塊或只使用通用異常處理程序

注意:

實質上,其中一些要點對於trigger_error也有效,實現起來要復雜一些。 嘗試catch塊非常容易使用,非常友好。

我認為這是一個例子可以說明我的觀點:

class HTMLParser {
    protected $doc;
    protected $source = null;
    public $parsedHtml;
    protected $parseErrors = array();
    public function __construct($doc) {
        if (!$doc instanceof DOMDocument) {
            // My Object is unusable without a valid DOMDOcument object
            // so I throw a CriticalException
            throw new CriticalException("Could not create Object Foo. You must pass a valid DOMDOcument object as parameter in the constructor");
        }
        $this->doc = $doc;
    }

    public function setSource($source) {
        if (!is_string($source)) {
            // I expect $source to be a string but was passed something else so I throw an exception
            throw new InvalidArgumentException("I expected a string but got " . gettype($source) . " instead");
        }
        $this->source = trim($source);
        return $this;
    }

    public function parse() {
        if (is_null($this->source) || $this->source == '') {
            throw new EmptyStringException("Source is empty");
        }
        libxml_use_internal_errors(true);
        $this->doc->loadHTML($this->source);
        $this->parsedHtml = $this->doc->saveHTML();
        $errors = libxml_get_errors();
        if (count($errors) > 0) {
            $this->parseErrors = $errors;
            throw new HtmlParsingException($errors[0]->message,$errors[0]->code,null,
                $errors[0]->level,$errors[0]->column,$errors[0]->file,$errors[0]->line);
        }
        return $this;
    }

    public function getParseErrors() {
        return $this->parseErrors;
    }

    public function getDOMObj() {
        return clone $this->doc;
    }
}

說明

構造函數中,如果傳遞的param不是DOMDocument類型,則拋出一個CriticalException ,因為如果沒有它,我的庫將根本不起作用。

(注意:我可以簡單地編寫__construct(DOMDocument $doc)但這只是一個例子)。

setsource()方法中,如果傳遞的參數不是字符串,則拋出InvalidArgumentException 。 我更喜歡在這裡暫停庫執行,因為source屬性是我的類的基本屬性,無效值將在我的庫中傳播錯誤。

parse()方法通常是循環中調用的最後一個方法。 即使我在libXML找到格式錯誤的文檔時拋出XmlParsingException ,也會首先完成解析並且結果可用(在某種程度上)。

處理示例庫

以下是如何處理這個組成庫的示例:

$source = file_get_contents('http://www.somehost.com/some_page.html');
try {
    $parser = new HTMLParser(new DOMDocument());
    $parser->setSource($source)
           ->parse();
} catch (CriticalException $e) {
    // Library failed miserably, no recover is possible for it.
    // In this case, it's prorably my fault because I didn't pass
    // a DOMDocument object.
    print 'Sorry. I made a mistake. Please send me feedback!';
} catch (InvalidArgumentException $e) {
    // the source passed is not a string, again probably my fault.
    // But I have a working parser object. 
    // Maybe I can try again by typecasting the argument to string
    var_dump($parser);
} catch (EmptyStringException $e) {
    // The source string was empty. Maybe there was an error
    // retrieving the HTML? Maybe the remote server is down?
    // Maybe the website does not exist anymore? In this case,
    // it isn't my fault it failed. Maybe I can use a cached
    // version?
    var_dump($parser);
} catch (HtmlParsingException $e) {
    // The html suplied is malformed. I got it from the interwebs
    // so it's not my fault. I can use $e or getParseErrors() 
    // to see if the html (and DOM Object) is usable
    // I also have a full functioning HTMLParser Object and can
    // retrieve a "loaded" functioning DOMDocument Object
    var_dump($parser->getParseErrors());
    var_dump($parser->getDOMObj());
}
$var = 'this will print wether an exception was previously thrown or not';
print $var;

您可以進一步採取這種做法並嵌套try catch塊,鏈異常,在確定的異常鏈路徑後運行選擇性代碼,選擇性記錄等等......

作為旁注,使用Exceptions並不意味著PROGRAM執行將停止,它只是意味著將繞過依賴於我的對象的代碼。 這取決於我或第三方開發人員隨時隨地使用它。


在第三方應用程序集成時代,使用異常並不是一個好主意

因為,當您嘗試將應用程序與其他應用程序或其他人的應用程序集成在一起時,您的整個應用程序將在某個第三方插件中的類引發異常時停止。 即使你有完整的錯誤處理,在你自己的應用程序中實現日誌記錄,第三方插件中某人的隨機對象將拋出異常,你的整個應用程序將停在那裡。

即使您的應用程序中有手段來彌補您正在使用的庫的錯誤 ....

示例中的案例可能是第三方社交登錄庫,它會引發異常,因為社交登錄提供程序返回了錯誤,並且不必要地殺死整個應用程序 - 順便說一下,hybridauth。 那麼,你有一個完整的應用程序,你有一個庫為你帶來了額外的功能 - 在這種情況下,社交登錄 - 即使你有很多後備的東西,提供商不認證(你自己的登錄系統,再加上20個左右的其他社交登錄提供商),您的整個應用程序將停止運行。 並且您最終將不得不更改第三方庫以解決這些問題,並且使用第三方庫加速開發的重點將會丟失。

對於PHP中處理錯誤的哲學而言,這是一個嚴重的設計缺陷。 讓我們面對它 - 在今天開發的大多數應用程序的另一端,有一個用戶。 無論是內聯網用戶,無論是互聯網上的用戶,還是系統管理員,都無所謂 - 通常都是用戶。

並且,有一個應用程序死在你的臉上沒有任何你可以做的事情,除了回到上一頁並在黑暗中拍攝你想要做的事情,作為一個用戶,是壞的,發展方面的不良做法。 更不用說,只有開發人員應該知道的內部錯誤由於許多原因(從可用性到安全性)被拋到用戶的臉上。

因此,我將不得不放棄特定的第三方庫(在這種情況下是hybridauth)而不是在我的應用程序中使用它,僅僅是因為這個原因。 儘管hybridauth是一個非常好的庫,並且顯然已經花費了很多精力,並且具有功能上的phletora。

因此,您應該避免在代碼中使用異常。 即使您現在正在執行的代碼是運行應用程序的頂級代碼,而不是庫,您可能希望將全部或部分代碼包含在其他項目中,或者必須集成部件或其全部與您的其他代碼或第三方代碼。 如果你使用異常,你將會遇到同樣的情況 - 即使你有適當的方法處理一段代碼提供的任何問題,整個應用程序/集成也會在你的臉上消失。


我喜歡使用異常的想法,但我經常涉及第三方庫,然後如果他們不使用異常,最終會有3-4種不同的方法解決問題! Zend使用例外。 CakePHP使用自定義錯誤處理程序,大多數PEAR庫使用PEAR :: Error對象。

我在這方面有一個真正的方法。 在這種情況下,自定義錯誤處理程序路由可能是最靈活的。 如果您只使用自己的代碼或使用使用它們的庫,則例外是一個好主意。

不幸的是,在PHP世界中,我們仍然厭倦了PHP4的死亡,所以像異常這樣的東西,雖然它們可能代表最佳實踐,但是每個人仍然在寫東西以便能夠在兩個方面工作時都非常慢。 5.希望這個崩潰現在已經結束,但到了它的時候,我們將面臨6到5之間的緊張局面......

/我掌握在手中......


異常是發出錯誤情況/異常情況的現代且可靠的方式。 使用它們 :)


這取決於實際情況。 在編寫業務邏輯/應用程序內部時,我傾向於使用異常,而對於Validator和類似的東西,我傾向於使用trigger_error。

在邏輯級別使用Exceptions的專家是允許您的應用程序在出現此類錯誤時執行此操作。 您允許應用程序選擇而不是讓業務邏輯知道如何呈現錯誤。

例如,使用trigger_error for Validator和這種性質的東西的專家是

try {
    $user->login();
}  catch (AuthenticationFailureException $e) {
    set_error_handler("my_login_form_handler");
    trigger_error("User could not be logged in. Please check username and password and try again!");
} catch (PersistenceException $pe) { // database unavailable
    set_error_handler("my_login_form_handler"); 
    trigger_error("Internal system error. Please contact the administrator.");
}

其中my_login_form_handler將字符串放在一邊,並將元素放在登錄表單上方的可見區域中。


顯然,沒有“一種正確的方式”,但對此有很多意見。 ;)

我個人使用trigger_error來處理異常無法做到的事情,即通知和警告(即你想要記錄的東西,但不能像錯誤/異常那樣停止應用程序的流程(即使你在某種程度上捕獲它們) ))。

我也主要對假定為不可恢復的條件(對發生異常的方法的調用者)使用異常,即嚴重錯誤。 我不使用異常作為返回具有相同含義的值的替代方法,如果這可能是非複雜的方式。 例如,如果我創建一個查找方法,我通常會返回一個空值,如果它找不到它要查找的內容而不是拋出一個EntityNotFoundException(或等效的)。

所以我的經驗法則是這樣的:

  • 只要找不到合適的結果,我發現返回和檢查空值(或其他一些默認值)要比使用try-catch子句處理它更容易。
  • 另一方面,如果沒有發現它是一個嚴重的錯誤,不屬於調用者的範圍,我仍然會拋出異常。

在後一種情況下拋出異常(而不是觸發錯誤)的原因是異常更具表現力,因為您使用了正確命名的Exception子類。 我發現在決定使用哪些例外時,使用PHP的標準庫的例外是一個很好的起點: http://www.php.net/~helly/php/ext/spl/classException.htmlhttp://www.php.net/~helly/php/ext/spl/classException.html

但是,您可能希望擴展它們以獲得針對特定情況的更具語義正確的異常。





exception