做任何一种方式的优点/缺点是什么。有一种正确的方式(tm)吗?
9 回答
如果您想在整个应用程序中使用异常而不是错误,您可以使用ErrorException和自定义错误处理程序来实现(请参阅 ErrorException 页面以获取示例错误处理程序)。这种方法唯一的缺点是非致命错误仍然会抛出异常,除非被捕获,否则这些异常总是致命的。基本上,如果您的error_reporting设置没有抑制它们,甚至E_NOTICE
会停止您的整个应用程序。
在我看来,使用 ErrorException 有几个好处:
- 自定义异常处理程序将允许您使用set_exception_handler显示漂亮的消息,即使是错误消息。
- 它不会以任何方式破坏现有代码... trigger_error和其他错误函数仍将正常工作。
- 很难忽略触发
E_NOTICE
s 和E_WARNING
s 的愚蠢编码错误。 您可以使用
try
/catch
来包装可能会生成 PHP 错误(不仅仅是异常)的代码,这是避免使用@
错误抑制 hack 的好方法:try { $foo = $_GET['foo']; } catch (ErrorException $e) { $foo = NULL; }
如果您想在发生任何未捕获的错误时向用户显示友好的消息,您可以将整个脚本包装在单个
try
/块中。catch
(请谨慎执行此操作,因为只记录未捕获的错误和异常。)
您应该在“异常情况”中使用异常,即当您调用方法 doFoo() 时,您应该期望它执行,如果由于某种原因 doFoo 无法完成它的工作,那么它应该引发异常。
很多旧的 php 代码在发生故障时会采用返回 false 或 null 的方法,但这使调试变得困难,异常使调试变得容易得多。
例如,假设您有一个名为 getDogFood() 的方法,它返回一个 DogFood 对象数组,如果您调用此方法并在出现问题时返回 null,您的调用代码将如何判断是否返回 null,因为存在错误还是没有可用的狗粮?
关于处理使用 php 内置错误日志记录的遗留代码库,您可以使用 set_error_handler() 函数覆盖错误日志记录,然后您可以使用该函数重新抛出通用异常。
现在您的所有代码都抛出了详细的异常,您可以自由决定如何处理它们,在代码的某些部分您可能希望捕获它们并尝试替代方法,或者您可以使用自己的日志记录函数记录它们可能会登录到数据库、文件、电子邮件 - 无论您喜欢哪个。简而言之——异常更加灵活。
我喜欢使用异常的想法,但我经常涉及第三方库,然后如果他们不使用异常,您最终会使用 3-4 种不同的方法来解决问题!Zend 使用异常。CakePHP 使用自定义错误处理程序,大多数 PEAR 库都使用 PEAR::Error 对象。
我在这方面有一种真正的方式。在这种情况下,自定义错误处理程序路由可能是最灵活的。如果您要么只使用自己的代码,要么使用使用它们的库,例外是一个好主意。
不幸的是,在 PHP 世界中,我们仍然遭受着拒绝放弃 PHP4 的痛苦,所以像异常之类的东西,虽然它们可能代表最佳实践,但在每个人仍在编写能够同时在这 4和 5. 希望这场灾难现在已经结束,尽管到那时,我们将在 6 和 5 之间出现紧张局势......
/我双手抱头...
异常的想法很优雅,并且使错误处理过程如此顺畅。但这仅适用于您有适当的异常类并且在团队开发中,更重要的一件事是“标准”异常。所以如果你打算使用异常,你最好先标准化你的异常类型,或者更好的选择是使用一些流行框架的异常。适用于 PHP 的另一件事(您可以在其中编写代码面向对象与结构代码相结合)是,如果您正在使用类编写整个应用程序。如果您正在编写面向对象,那么异常肯定会更好。毕竟我认为你的错误处理过程会比 trigger_error 和其他东西更顺畅。
这取决于实际情况。当我编写业务逻辑/应用程序内部时,我倾向于使用异常,而对于验证器和类似的东西,我倾向于使用 trigger_error。
在逻辑级别使用异常的优点是允许您的应用程序在发生此类错误时执行。您允许应用程序进行选择,而不是让业务逻辑知道如何呈现错误。
例如,将 trigger_error 用于 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.html
但是,您可能希望扩展它们以针对您的特定情况获得更多语义正确的异常。
介绍
以我个人的经验,作为一般规则,我更喜欢在我的代码中使用 Exceptions 而不是 trigger_error。这主要是因为使用异常比触发错误更灵活。而且,恕我直言,这不仅对我自己和第 3 方开发人员都有好处。
- 我可以扩展 Exception 类(或使用异常代码)来明确区分我的库的状态。这有助于我和第 3 方开发人员处理和调试代码。这也揭示了它在不需要源代码浏览的情况下会在哪里以及为什么会失败。
- 我可以有效地停止我的库的执行,而无需停止脚本的执行。
- 第 3 方开发人员可以链接我的异常(在 PHP > 5.3.* 中)对于调试非常有用,并且在处理我的库由于不同原因而失败的情况时可能会很方便。
我可以做到这一切,而不用强加他应该如何处理我的图书馆故障。(即:创建复杂的错误处理函数)。他可以使用 try catch 块或只使用通用异常处理程序
笔记:
其中一些点,本质上对 trigger_error 也是有效的,只是实现起来稍微复杂一些。Try 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;
}
}
解释
在构造函数CriticalException
中,如果传递的参数不是类型,我会抛出一个,DOMDocument
因为没有它我的库将根本无法工作。
(注意:我可以简单地写__construct(DOMDocument $doc)
,但这只是一个例子)。
在方法中,如果传递的参数不是字符串,setsource()
我会抛出一个。InvalidArgumentException
我更喜欢在这里停止库执行,因为源属性是我的类的基本属性,并且无效值会将错误传播到我的库中。
该parse()
方法通常是循环中调用的最后一个方法。即使我抛出一个XmlParsingException
if libXML 发现格式错误的文档,解析首先完成并且结果可用(在一定程度上)。
处理示例库
这是一个如何处理这个组成的库的示例:
$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 块、链接异常、按照确定的异常链路径运行选择性代码、选择性日志记录等......
作为旁注,使用异常并不意味着程序执行将停止,它只是意味着依赖于我的对象的代码将被绕过。由我或第 3 方开发人员随心所欲地处理它。
异常是指示错误情况/异常情况的现代且稳健的方式。使用它们 :)
在 3rd 方应用程序集成时代,使用异常并不是一个好主意。
因为,当您尝试将您的应用程序与其他应用程序或其他人的应用程序与您的应用程序集成时,您的整个应用程序将在某个 3rd 方插件中的类抛出异常时停止。即使您有完整的错误处理,在您自己的应用程序中实现日志记录,第三方插件中某人的随机对象也会抛出异常,并且您的整个应用程序将停在那里。
即使您的应用程序有办法弥补您正在使用的那个库的错误......
一个例子可能是第 3 方社交登录库,它抛出异常,因为社交登录提供程序返回了一个错误,并且不必要地杀死了你的整个应用程序 - 顺便说一下,hybridauth。所以,你有一个完整的应用程序,你有一个库为你带来了额外的功能——在这种情况下,社交登录——即使你有很多后备的东西,以防提供者没有进行身份验证(你自己的登录系统,再加上大约 20 个其他社交登录提供商),您的整个应用程序将完全停止。而且您最终将不得不更改 3rd 方库来解决这些问题,并且使用 3rd 方库来加速开发的意义将丢失。
在 PHP 中处理错误的理念方面,这是一个严重的设计缺陷。让我们面对现实吧——在当今开发的大多数应用程序的另一端,都有一个用户。无论是 Intranet 用户,还是 Internet 上的用户,还是系统管理员,都无所谓 - 通常有一个用户。
而且,让一个应用程序死在你的脸上,而此时你除了返回上一页并在黑暗中对你正在尝试做的事情一无所知之外,你无能为力,作为用户,这是不好的,开发方面的不良做法。更不用说,由于许多原因(从可用性到安全性)而只有开发人员应该知道的内部错误被抛出到用户的脸上。
结果,我将不得不放弃特定的第 3 方库(在本例中为 hybridauth),而不是在我的应用程序中使用它,仅出于这个原因。尽管hybridauth 是一个非常好的库,并且显然已经在它上面花费了很多精力,并且具有大量的功能。
因此,您应该避免在代码中使用异常。即使您现在正在执行的代码是将运行您的应用程序的顶级代码,而不是库,您也可能希望将全部或部分代码包含在其他项目中,或者必须集成部分或全部与您的其他代码或第 3 方代码。如果你使用异常,你最终会遇到同样的情况——即使你有适当的方法来处理一段代码提供的任何问题,整个应用程序/集成都会死在你的面前。