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

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

In PHP5, should I use Exceptions or trigger_error/set_error_handler?

两种方式的利弊是什么? 有没有正确的方法?


如果要在整个应用程序中使用异常而不是错误,则可以使用ErrorException和自定义错误处理程序来实现(有关示例错误处理程序,请参见ErrorException页面)。此方法的唯一缺点是非致命错误仍会引发异常,除非捕获异常,否则异常总是致命的。基本上,如果您的error_reporting设置不禁止它们,甚至E_NOTICE都将停止整个应用程序。

我认为使用ErrorException有几个好处:

  • 自定义的异常处理程序将使您可以使用set_exception_handler显示漂亮的消息,甚至显示错误。
  • 它不会以任何方式破坏现有代码... trigger_error和其他错误功能仍将正常运行。
  • 这真的很难忽略触发E_NOTICEE_WARNING的愚蠢的编码错误。
  • 您可以使用try / catch来包装可能产生PHP错误(不仅仅是异常)的代码,这是避免使用@错误抑制技巧的好方法:

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


  • 您应该在"特殊情况"下使用异常,也就是说,当您调用方法doFoo()时,您应该期望它执行,如果由于某种原因doFoo无法执行其工作,则应引发异常。

    许多旧的php代码都会采用在发生故障时返回false或null的方法,但这使事情难以调试,异常使此调试更加容易。

    例如,假设您有一个名为getDogFood()的方法,该方法返回了DogFood对象的数组,如果您调用此方法,并且在出现问题时返回null,那么调用代码将如何判断是否由于错误而返回了null还是没有狗粮?

    关于使用php的内置错误日志记录的遗留代码库,您可以使用set_error_handler()函数覆盖错误日志记录,该函数可用于随后抛出通用Exception。

    既然您已拥有所有引发详细异常的代码,则可以自由决定如何处理它们,在代码的某些部分中,您可能希望捕获它们并尝试其他方法,或者可以使用自己的日志记录功能对其进行记录可能会登录到数据库,文件或电子邮件-随便哪个。简而言之,例外更为灵活。


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

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

    不幸的是,在PHP世界中,我们仍然遭受着死于PHP4的苦难,因此异常之类的东西虽然代表了最佳实践,但是在所有人都仍在编写能够同时在这两者中工作的东西的过程中,进展却异常缓慢。4和5.希望这场崩溃现在可以结束了,尽管到那时,我们将在6到5之间紧张起来...

    /我握着头...


    介绍

    根据我的个人经验,通常,我更喜欢在代码中使用Exception而不是trigger_error。这主要是因为使用异常比触发错误更灵活。而且,恕我直言,这不仅对我自己还是对第三方开发人员都是有益的。

  • 我可以扩展Exception类(或使用异常代码)来显式区分库的状态。这有助于我和第三方开发人员处理和调试代码。这也揭示了在不需要源代码浏览的情况下失败的位置和原因。
  • 我可以有效地停止库的执行,而不必停止脚本的执行。
  • 第三方开发人员可以链接我的Exceptions(在PHP> 5.3。*中),这对于调试非常有用,并且在处理由于各种原因导致我的库可能失败的情况下可能很方便。
  • 而且我可以做所有这一切,而不必强加他应该如何处理我的库故障。 (即:创建复杂的错误处理功能)。他可以使用try catch块,也可以只使用通用异常处理程序

    注意:

    从本质上讲,这些要点中的某些点对于trigger_error也有效,实现起来稍微复杂一点。尝试catch块确实非常易于使用,并且对代码非常友好。

    我认为这是一个例子,可以说明我的观点:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    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;
        }
    }

    说明

    在构造函数中,如果传递的参数不是DOMDocument类型,则抛出CriticalException,因为没有它,我的库将根本无法工作。

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

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

    parse()方法通常是循环中最后调用的方法。即使libXML找到格式错误的文档,我抛出了XmlParsingException,分析仍首先完成并且结果可用(在一定程度上)。

    处理示例库

    这是一个如何处理这个组成的库的示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    $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;

    您可以进一步进行并嵌套尝试捕获块,链异常,按照确定的异常链路径运行选择性代码,选择性日志记录等。

    附带说明一下,使用Exceptions并不意味着PROGRAM的执行将停止,而只是意味着将绕过依赖于我的对象的代码。由我或第三方开发人员自行决定。


    显然,没有"正确的方法",但是对此有很多意见。 ;)

    就个人而言,我将trigger_error用于异常无法完成的事情,即通知和警告(即您要记录的内容,但不能以与错误/异常相同的方式停止应用程序的流(即使您在某种程度上捕获了它们) ))。

    我还通常将异常用于假定是不可恢复的条件(发生异常的方法的调用方),即严重错误。我不使用异常来替代返回具有相同含义的值的方式,如果可以通过非卷积方式实现的话。例如,如果创建一个查找方法,则通常会在没有找到要查找的内容时返回空值,而不是抛出EntityNotFoundException(或等效项)。

    所以我的经验法则是这样的:

    • 只要找不到某件事是合理的结果,我发现返回和检查空值(或其他默认值)要比使用try-catch-clause处理它容易得多。
    • 另一方面,如果没有发现严重错误而不属于调用方可以恢复的错误,我仍然会引发异常。

    在后一种情况下引发异常的原因(与触发错误相反)是,假设您使用正确命名的Exception子类,则异常的表达性更高。我发现在决定使用哪种异常时,使用PHP标准库的异常是一个很好的起点:http://www.php.net/~helly/php/ext/spl/classException.html

    但是,您可能需要扩展它们以获取针对您的特定情况的更多语义正确的异常。


    这取决于实际情况。当我编写业务逻辑/应用程序内部时,我倾向于使用Exceptions,而对于Validator和类似的东西,则使用trigger_error。

    在逻辑级别使用Exceptions的优点是允许您的应用程序在发生此类错误的情况下执行此操作。您允许应用程序选择而不是让业务逻辑知道如何显示错误。

    比如说,使用Trigger_error作为Validator的专业人士以及具有这种性质的事物,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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修饰字符串并将元素放置在登录表单上方的可见区域中。


    异常的想法很优雅,并且使错误处理过程非常顺利。但这仅在您具有适当的异常类并且在团队开发中适用时,更重要的是"标准"异常。因此,如果您打算使用异常,则最好先对异常类型进行标准化,或者更好的选择是使用一些流行框架中的异常。适用于PHP的另一件事(您可以在其中编写结合结构代码的代码对象定向器)是,如果要使用类编写整个应用程序。如果您正在编写面向对象的代码,那么肯定可以更好地使用异常。毕竟,我认为您的错误处理流程比trigger_error和其他东西要平滑得多。


    异常是一种现代且健壮的方式来表示错误情况/异常情况。使用它们 :)


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

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

    即使您在应用程序中有能力弥补您正在使用的该库的错误,也可以...。

    例如,第三方社交登录库可能会抛出异常,因为社交登录提供程序返回了错误,并不必要地杀死了您的整个应用程序-hybridauth。因此,这里有一个完整的应用程序,那里有一个库,为您带来了附加功能-在这种情况下为社交登录-即使在提供商未通过身份验证的情况下有很多后备功能(您自己的)登录系统,再加上20个左右的其他社交登录提供商),您的ENTIRE应用程序将陷入停顿。您最终将不得不更改第三方库来解决这些问题,而使用第三方库来加速开发的意义将丢失。

    就PHP处理错误的哲学而言,这是一个严重的设计缺陷。面对现实吧-在当今开发的大多数应用程序的另一端,有一个用户。无论是Intranet用户,是Internet用户,还是sysadmin,都没有关系-通常有一个用户。

    而且,让应用程序死在您的脸上,除了回到上一页并对您作为用户试图做的事情一无所知之外,那时您无能为力,这是不好的,开发方面的不良做法。更不用说,由于许多原因(从可用性到安全性),开发人员只能在用户脸上抛出内部错误。

    结果,仅出于这个原因,即时消息我就不得不放弃一个特定的第三方库(在这种情况下为hybridauth),而不能在我的应用程序中使用它。尽管hybridauth是一个非常好的库,并且显然已经花了很多心血,但功能很多。

    因此,您应该避免在代码中使用异常。即使您现在正在执行的代码是将运行您的应用程序的顶级代码,而不是库,您也可能希望将所有或部分代码包含在其他项目中,或者必须集成部分或与您自己的其他代码或第三方代码一起使用。而且,如果您使用了异常,则最终将面临相同的情况-即使您有适当的方法来处理一段代码所提供的任何问题,整个应用程序/集成也将死在眼前。


    推荐阅读