在PHP5中,我应该使用Exceptions还是trigger_error / set_error_handler?

时间:2008-09-13 15:18:54

标签: php exception

做任何一种方式的优点/缺点是什么。有没有一条正确的方法(tm)?

9 个答案:

答案 0 :(得分:20)

如果要为整个应用程序使用异常而不是错误,可以使用ErrorException和自定义错误处理程序(请参阅ErrorException页面以获取示例错误处理程序)。这种方法唯一的缺点是非致命错误仍会抛出异常,除非被捕获,否则这些异常总是致命的。基本上,如果您的error_reporting设置无法抑制它们,即使E_NOTICE也会停止整个应用程序。

在我看来,使用ErrorException有几个好处:

  1. 使用set_exception_handler
  2. ,自定义异常处理程序可让您显示不错的消息,甚至是错误消息
  3. 它不会以任何方式破坏现有代码...... trigger_error并且其他错误函数仍然可以正常工作。
  4. 这使得忽略触发E_NOTICEE_WARNING s的愚蠢编码错误变得非常困难。
  5. 您可以使用try / catch来包装可能生成PHP错误的代码(不仅仅是异常),这是避免使用@错误的好方法抑制黑客:

    try {
        $foo = $_GET['foo'];
    } catch (ErrorException $e) {
        $foo = NULL;
    }
    
  6. 如果您希望在发生任何未捕获的错误时向用户显示友好消息,则可以将整个脚本包装在单个try / catch块中。 (请仔细执行此操作,因为仅记录未捕获的错误和异常。)

答案 1 :(得分:8)

你应该在“异常情况”中使用异常,也就是当你调用一个你应该期望它执行的方法doFoo()时,如果由于某种原因doFoo无法完成它的工作那么它应该引发异常。

许多旧的PHP代码会在发生故障时采用返回false或null的方法,但这会使调试变得困难,异常会使调试变得更加容易。

例如,假设您有一个名为getDogFood()的方法,它返回了一个DogFood对象数组,如果您调用此方法并在出现错误时返回null,您的调用代码将如何判断是否返回null,因为那里是错误还是没有狗食?

关于处理使用php内置错误日志记录的遗留代码库,您可以使用set_error_handler()函数覆盖错误日志记录,然后可以使用该函数重新抛出一般异常。

现在您已经将所有代码抛出了详细的异常,您可以自由决定如何处理它们,在代码的某些部分中,您可能希望捕获它们并尝试其他方法,或者您可以使用自己的方法记录它们记录功能,可能会记录到数据库,文件,电子邮件 - 无论您喜欢哪个。简而言之 - 例外更灵活。

答案 2 :(得分:6)

我喜欢使用异常的想法,但我经常涉及第三方库,如果他们不使用异常,最终会遇到3-4种不同的问题方法! Zend使用例外。 CakePHP使用自定义错误处理程序,大多数PEAR库使用PEAR :: Error对象。

我在这方面有一个真正的方法。在这种情况下,自定义错误处理程序路由可能是最灵活的。如果您只使用自己的代码或使用使用它们的库,则例外是一个好主意。

不幸的是,在PHP世界中,我们仍然遭受拒绝死于PHP4的困扰,所以像异常这样的东西,虽然它们可能代表最佳实践,但是当每个人都在写东西以便能够工作时,这种情况非常缓慢在4和5中,希望这个崩溃现在已经结束,但到了它的时候,我们将会在6到5之间出现紧张局势......

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

答案 3 :(得分:3)

这取决于具体情况。在编写业务逻辑/应用程序内部时,我倾向于使用Exceptions,而对于Validator和类似的东西,我倾向于使用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将字符串清空并将元素放在登录表单上方的可见区域中。

答案 4 :(得分:3)

显然,没有“一条正确的方法”,但对此有很多意见。 ;)

我个人使用trigger_error来解决异常无法做到的事情,即通知和警告(即你想要记录的东西,但不能以与错误/异常相同的方式停止应用程序的流程(即使你抓住它们)在某种程度上))。

我还主要对假定为不可恢复的条件(对发生异常的方法的调用者)使用异常,即严重错误。如果可以以非复杂的方式进行,我不会使用异常作为返回具有相同含义的值的替代方法。例如,如果我创建一个查找方法,我通常会返回一个空值,如果它找不到它要查找的内容而不是抛出一个EntityNotFoundException(或等效的)。

所以我的经验法则是:

  • 只要找不到合适的结果,我就会发现返回并检查空值(或其他默认值)要比使用try-catch子句处理它更容易。
  • 另一方面,如果没有发现这是一个严重的错误,不属于调用者的范围,我仍然会抛出异常。

在后一种情况下抛出异常(而不是触发错误)的原因是异常更具表现力,因为您使用了正确命名的Exception子类。我发现在决定使用哪些例外时,使用PHP标准库的例外是一个很好的起点:http://www.php.net/~helly/php/ext/spl/classException.html

但是,您可能希望对它们进行扩展,以便为您的特定情况获得更多语义正确的异常。

答案 5 :(得分:3)

简介

根据我的个人经验,作为一般规则,我更喜欢在我的代码中使用Exceptions而不是trigger_error。这主要是因为使用Exceptions比触发错误更灵活。而且,恕我直言,这对我和第三方开发者来说也是有益的。

  1. 我可以扩展Exception类(或使用异常代码)来明确区分我的库的状态。这有助于我和第三方开发人员处理和调试代码。 这也揭示了它可以失败的地方和原因,而无需浏览源代码。
  2. 我可以在不停止执行脚本的情况下有效地暂停我的库的执行。
  3. 第三方开发人员可以链接我的例外(在PHP> 5.3。*中)。对于调试非常有用,并且在处理我的库可能由于不同原因而失败的情况时可能很方便。
  4. 我可以做到这一切,而不会强加他应该如何处理我的库故障。 (即:创建复杂的错误处理函数)。他可以使用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;
        }
    }
    

    说明

    构造函数中,如果传递的参数不是 CriticalException 类型,则抛出 DOMDocument ,因为没有我的图书馆根本不会工作。

    (注意:我可以简单地写__construct(DOMDocument $doc),但这只是一个例子。)

    setsource() 方法中,如果传递的参数不是字符串,则抛出 InvalidArgumentException 。我更喜欢在这里停止库执行,因为源属性是我的类的基本属性,无效值将在我的库中传播错误。

    parse() 方法通常是循环中调用的最后一个方法。即使我发现 XmlParsingException ,如果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块,链异常,在确定的异常链路径后运行选择性代码,选择性记录等等......

    作为旁注,使用Exceptions并不意味着PROGRAM执行将停止,它只是意味着将绕过依赖于我的对象的代码。这取决于我或第三方开发人员随时随地使用它。

答案 6 :(得分:2)

异常的想法很优雅,使错误处理过程如此顺利。但这只适用于有适当的异常类和团队开发时,更重要的是“标准”异常。因此,如果您计划使用异常,则最好首先标准化您的异常类型,或者更好的选择是使用某些流行框架中的异常。另一个适用于PHP(你可以编写你的代码对象orienter结合结构代码)的东西是,如果你用类编写整个应用程序。如果您正在编写面向对象,那么异常肯定会更好。毕竟我认为你的错误处理过程会比trigger_error和stuff更顺畅。

答案 7 :(得分:1)

异常是发出错误情况/异常情况的现代且强有力的方式。使用它们:)

答案 8 :(得分:-2)

在第三方应用程序集成时代,使用异常不是一个好主意

因为,当您尝试将应用程序与其他应用程序或其他人的应用程序集成在一起时,您的整个应用程序将在某个第三方插件中的某个类引发异常时停止。即使您有完整的错误处理,在您自己的应用程序中实现日志记录,第三方插件中某人的随机对象也会抛出异常,您的整个应用程序将停在那里。

即使您的应用程序中有手段来弥补您正在使用的库的错误 ....

示例中的案例可能是第三方社交登录库,它会抛出异常,因为社交登录提供程序返回错误,并且不必要地杀死整个应用程序 - 顺便说一下,hybridauth。所以,你有一个完整的应用程序,你有一个库为你带来了额外的功能 - 在这种情况下,社交登录 - 即使你有很多后备的东西,提供商不认证(你自己的登录系统,加上20个左右的其他社交登录提供商),您的整个应用程序将停止运行。并且您最终将不得不更改第三方库以解决这些问题,并且使用第三方库加速开发的重点将会丢失。

这是处理PHP错误的哲学方面的一个严重的设计缺陷。让我们面对它 - 在今天开发的大多数应用程序的另一端,有一个用户。无论是内联网用户,还是互联网上的用户,无论是系统管理员,都无所谓 - 通常都是用户。

并且,如果有一个应用程序死在你的脸上而没有你可以做的任何事情,除了回到上一页并在黑暗中拍摄你正在尝试做的事情,作为用户,从开发方面来说是糟糕的坏习惯。更不用说,只有开发人员应该知道的内部错误由于许多原因(从可用性到安全性)被抛到用户的脸上。

因此,我将不得不放弃特定的第三方库(在这种情况下是hybridauth)而不是在我的应用程序中使用它,仅仅是因为这个原因。尽管hybridauth是一个非常好的库,并且显然已经花费了大量精力,并且具有强大的功能。

因此,您应该避免在代码中使用异常。即使您现在正在执行的代码是运行应用程序的顶级代码,而不是库,您可能希望将全部或部分代码包含在其他项目中,或者必须集成部件或其全部与您的其他代码或第三方代码。如果你使用异常,你最终会遇到同样的情况 - 即使你有适当的方法处理一段代码所提供的任何问题,整个应用程序/集成也会在你的脸上消失。