PHP:异常还是错误?


Answers:


87

引发异常 -旨在捕获异常。错误通常是无法恢复的。例如,让我们说-您有一段代码将在数据库中插入一行。此调用有可能失败(重复的ID)-您将要遇到一个“错误”,在这种情况下是“异常”。当您插入这些行时,您可以执行以下操作

try {
  $row->insert();
  $inserted = true;
} catch (Exception $e) {
  echo "There was an error inserting the row - ".$e->getMessage();
  $inserted = false;
}

echo "Some more stuff";

程序执行将继续-因为您“捕获”了异常。除非捕获到异常,否则它将被视为错误。它也会使您在失败后继续执行程序。


29
Errors are generally unrecoverable<-实际上,这不是真的。E_ERROR并且E_PARSE是两种最常见的不可恢复的错误(也有其他几个),但绝大多数你会看到错误在开发中被收回(E_NOTICEE_WARNING等)。不幸的是,PHP的错误处理完全是一团糟-各种各样的事情都不必要地触发错误(例如,绝大多数文件系统功能)。通常,“ OOP方式”是例外,但是不幸的是,某些PHP的本机OOP API使用错误而不是例外:-(
DaveRandom 2013年

1
@DaveRandom E_NOTICE,E_WARNING不是定义上的“错误”吗?我一直认为它们是PHP的“消息”显示,以通知程序员他/他编写的代码可能有问题。
slhsen

2
@slhsen,问题确实是糟糕的术语,所有这些消息的形式都通过PHP中的“错误处理系统”,从语义上讲,所有这些事件都是“错误”,即使从语义上讲,警告/警告与“错误”。幸运的是,即将到来的PHP7至少通过将大多数东西变成可捕获的异常(通过新Throwable接口),为解决这些混乱铺平了道路,从而提供了一种更具表现力和绝对性的方式来区分并正确处理这两种情况。问题和咨询消息
DaveRandom

我想“程序执行将继续”已更改吗?由于PHP说:“当一个异常被抛出,下面的语句代码不会被执行”(php.net/manual/en/language.exceptions.php
罗伯特·辛克莱

1
我认为OP的含义更多是关于ErrorVS的后代与VS的后代之间的区别Exception
XedinUnknown

55

我通常会set_error_handler使用一个会发生错误并引发异常的函数,以便无论发生什么情况我都将需要处理异常。没有更多的@file_get_contents只是很好而整洁的尝试/捕获。

在调试情况下,我还有一个异常处理程序,可输出一个asp.net之类的页面。我正在路上发布此消息,但如果需要,我将在稍后发布示例源。

编辑:

除承诺的内容外,我还剪切并粘贴了一些代码以制作示例。我一直保存在下面的文件我的工作站上,可以不再看到结果这里(因为链路断开)。

<?php

define( 'DEBUG', true );

class ErrorOrWarningException extends Exception
{
    protected $_Context = null;
    public function getContext()
    {
        return $this->_Context;
    }
    public function setContext( $value )
    {
        $this->_Context = $value;
    }

    public function __construct( $code, $message, $file, $line, $context )
    {
        parent::__construct( $message, $code );

        $this->file = $file;
        $this->line = $line;
        $this->setContext( $context );
    }
}

/**
 * Inspire to write perfect code. everything is an exception, even minor warnings.
 **/
function error_to_exception( $code, $message, $file, $line, $context )
{
    throw new ErrorOrWarningException( $code, $message, $file, $line, $context );
}
set_error_handler( 'error_to_exception' );

function global_exception_handler( $ex )
{
    ob_start();
    dump_exception( $ex );
    $dump = ob_get_clean();
    // send email of dump to administrator?...

    // if we are in debug mode we are allowed to dump exceptions to the browser.
    if ( defined( 'DEBUG' ) && DEBUG == true )
    {
        echo $dump;
    }
    else // if we are in production we give our visitor a nice message without all the details.
    {
        echo file_get_contents( 'static/errors/fatalexception.html' );
    }
    exit;
}

function dump_exception( Exception $ex )
{
    $file = $ex->getFile();
    $line = $ex->getLine();

    if ( file_exists( $file ) )
    {
        $lines = file( $file );
    }

?><html>
    <head>
        <title><?= $ex->getMessage(); ?></title>
        <style type="text/css">
            body {
                width : 800px;
                margin : auto;
            }

            ul.code {
                border : inset 1px;
            }
            ul.code li {
                white-space: pre ;
                list-style-type : none;
                font-family : monospace;
            }
            ul.code li.line {
                color : red;
            }

            table.trace {
                width : 100%;
                border-collapse : collapse;
                border : solid 1px black;
            }
            table.thead tr {
                background : rgb(240,240,240);
            }
            table.trace tr.odd {
                background : white;
            }
            table.trace tr.even {
                background : rgb(250,250,250);
            }
            table.trace td {
                padding : 2px 4px 2px 4px;
            }
        </style>
    </head>
    <body>
        <h1>Uncaught <?= get_class( $ex ); ?></h1>
        <h2><?= $ex->getMessage(); ?></h2>
        <p>
            An uncaught <?= get_class( $ex ); ?> was thrown on line <?= $line; ?> of file <?= basename( $file ); ?> that prevented further execution of this request.
        </p>
        <h2>Where it happened:</h2>
        <? if ( isset($lines) ) : ?>
        <code><?= $file; ?></code>
        <ul class="code">
            <? for( $i = $line - 3; $i < $line + 3; $i ++ ) : ?>
                <? if ( $i > 0 && $i < count( $lines ) ) : ?>
                    <? if ( $i == $line-1 ) : ?>
                        <li class="line"><?= str_replace( "\n", "", $lines[$i] ); ?></li>
                    <? else : ?>
                        <li><?= str_replace( "\n", "", $lines[$i] ); ?></li>
                    <? endif; ?>
                <? endif; ?>
            <? endfor; ?>
        </ul>
        <? endif; ?>

        <? if ( is_array( $ex->getTrace() ) ) : ?>
        <h2>Stack trace:</h2>
            <table class="trace">
                <thead>
                    <tr>
                        <td>File</td>
                        <td>Line</td>
                        <td>Class</td>
                        <td>Function</td>
                        <td>Arguments</td>
                    </tr>
                </thead>
                <tbody>
                <? foreach ( $ex->getTrace() as $i => $trace ) : ?>
                    <tr class="<?= $i % 2 == 0 ? 'even' : 'odd'; ?>">
                        <td><?= isset($trace[ 'file' ]) ? basename($trace[ 'file' ]) : ''; ?></td>
                        <td><?= isset($trace[ 'line' ]) ? $trace[ 'line' ] : ''; ?></td>
                        <td><?= isset($trace[ 'class' ]) ? $trace[ 'class' ] : ''; ?></td>
                        <td><?= isset($trace[ 'function' ]) ? $trace[ 'function' ] : ''; ?></td>
                        <td>
                            <? if( isset($trace[ 'args' ]) ) : ?>
                                <? foreach ( $trace[ 'args' ] as $i => $arg ) : ?>
                                    <span title="<?= var_export( $arg, true ); ?>"><?= gettype( $arg ); ?></span>
                                    <?= $i < count( $trace['args'] ) -1 ? ',' : ''; ?> 
                                <? endforeach; ?>
                            <? else : ?>
                            NULL
                            <? endif; ?>
                        </td>
                    </tr>
                <? endforeach;?>
                </tbody>
            </table>
        <? else : ?>
            <pre><?= $ex->getTraceAsString(); ?></pre>
        <? endif; ?>
    </body>
</html><? // back in php
}
set_exception_handler( 'global_exception_handler' );

class X
{
    function __construct()
    {
        trigger_error( 'Whoops!', E_USER_NOTICE );      
    }
}

$x = new X();

throw new Exception( 'Execution will never get here' );

?>

那会有所帮助。任何可以简化我与PHP交往的时间的方法都将有所帮助。:-)
杰森·贝克

好的代码,谢谢。我不知道X类来自哪里,它的目的是什么?
亚历克(Alec)

“ set_exception_handler('global_exception_handler');”之下的所有内容 只是演示,您将不需要它,只是为了显示在正常的非异常错误情况下会发生什么。
克里斯(Kris)2009年

标准PHP专门定义了ErrorException,它是从常规错误处理程序中抛出的。您能让我编辑和更新您的帖子吗?
Tiberiu-Ionuț Stan 2013年

@ Tiberiu-IonuțStan:当然可以,但是工作示例将不同步。另外,现在我可能会指向人github.com/theredhead/red.web/blob/master/src/lib/bootstrap.phpprivate-void.com代替。
克里斯(Kris)2013年

21

答案值得谈论房间里的大象

错误是在运行时处理错误情况的旧方法。通常,代码将set_error_handler在执行某些代码之前调用类似的代码。遵循汇编语言中断的传统。这是一些BASIC代码的外观。

on error :divide_error

print 1/0
print "this won't print"

:divide_error

if errcode = X
   print "divide by zero error"

很难确保set_error_handler以正确的值调用它。更糟糕的是,可能会调用一个单独的过程来更改错误处理程序。再加上很多次,呼叫被点缀在set_error_handler呼叫和处理程序中。代码很容易迅速失控。通过规范好代码真正在做什么的语法和语义来抢占异常处理。

try {
   print 1/0;
   print "this won't print";
} catch (DivideByZeroException $e) {
   print "divide by zero error";
}

没有单独的功能,也没有调用错误错误处理程序的风险。现在,确保代码在同一位置。另外,我们还会收到更好的错误消息。

当许多其他语言已经演变为首选的异常处理模型时,PHP过去仅具有错误处理。最终,PHP的制造商实现了异常处理。但是它们很可能支持旧代码,因此保留了错误处理,并提供了一种使错误处理看起来像异常处理的方式。除此之外,不能保证某些代码不会重置错误处理程序,而这正是异常处理所要提供的。

最终答案

在实施异常处理之前编码的错误很可能仍然是错误。新错误可能是例外。但是没有错误或例外的设计或逻辑。它只是基于编码时可用的内容以及程序员对其进行编码的偏好。


3
这是异常和错误共存的真正原因。如果从头开始设计,则php仅应包含一个或另一个。
Tomas Zubiri

1
在我看来,这是最好的答案,因为它是最详细,最解释性的。
罗伯特·库兹尼尔

8

这里要添加的一件事是关于处理异常和错误。为了应用程序开发人员的目的,错误和异常都是要记录的“坏事”,以了解应用程序所存在的问题-从而使您的客户长期拥有更好的体验。

因此,编写一个错误处理程序与执行异常操作相同的事情是有意义的。


感谢您提供链接!
Mike Moore 2010年

@Alex Weinstein:链接已断开
Marco Demaio

7

如其他答案所述,将错误处理程序设置为异常抛出器是处理PHP中错误的最佳方法。我使用更简单的设置:

set_error_handler(function ($errno, $errstr, $errfile, $errline ) {
        if (error_reporting()) {
                throw new \ErrorException($errstr, 0, $errno, $errfile, $errline);
        }
});

请注意error_reporting()检查以保持@操作员的正常工作。另外,也不需要定义自定义异常,PHP为此提供了一个不错的类。

引发异常的最大好处是,异常具有与之关联的堆栈跟踪,因此很容易找到问题出在哪里。


5

回复:“但是错误和异常之间到底有什么区别?”

关于这里的差异,有很多很好的答案。我将添加尚未讨论的内容-性能。具体来说,这是因为抛出/处理异常与处理返回码(成功或某些错误)之间的差异。通常,在php中,这意味着返回falsenull,但是可以更详细地说明它们,例如文件上传:http : //php.net/manual/en/features.file-upload.errors.php您甚至可以返回Exception对象!

我已经完成了几种使用不同语言/系统进行的性能测试。一般来说,异常处理比检查错误返回码慢大约10,000倍。

因此,如果绝对要这样做,肯定需要在它开始之前就完成执行-很好,您不走运,因为不存在时间旅行。没有时间旅行,返回码是最快的选择。

编辑:

PHP对异常处理进行了高度优化。实际测试表明,抛出异常仅比返回值慢2-10倍。


3
当然可以,但是因抛出异常而损失的循环数量远远超过了获得异常获得的额外描述能力所弥补。您可以引发特定类型的异常,甚至可以向异常中添加数据以包含错误代码。我也非常怀疑您的10,000 *次索赔。即使您对时差的看法是正确的,在现实世界中,执行返回以及与新的执行,抛出和捕获相比所花费的时间与已执行的代码相比也是如此之小,以至于这绝对是过早的优化。抛出异常时,它们更适合90%的时间处理。
gnarf

1
1. 10,000x是准确的-根据语言和编译器选项有所不同。2.您不必返回null / false。您可以返回一个数字-最多可以返回MAX_ULONG个返回码。您也可以返回一个失败字符串,然后只检查成功字符串或int或null。3.在现实世界中,每个时钟周期都很重要。Facebook每天有5.52亿活跃用户。假设例外情况仅为2倍,并且检查用户/通行证的时间为.001s,则意味着每天可以节省153个小时的处理时间。10,000倍,可节省175年。仅用于检查登录尝试-每天。
evan 2012年

@evan:仅供参考,在这里他们测试了带有异常的代码,而且看起来并不慢:stackoverflow.com/a/445094/260080
Marco Demaio 2012年

@MarcoDemaio该问题仅涉及try / catch块,而不会引发异常。更好的测试方法是在noexcept()中返回一个值,并在except()中引发一个异常。而且,它应该通过多个功能冒泡。 stackoverflow.com/a/104375/505172指出,PHP的差异实际上是54倍。我进行了自己的实时测试,速度似乎慢了2到10倍。这一切都比预期的要好。
evan 2013年

@evan:那我就不用担心,我只使用异常来跟踪意外的/不可恢复的错误,因此即使它慢了100倍,我也不在乎。我担心的是仅通过添加try / catch块来使代码变慢。
Marco Demaio 2013年

4

我认为您正在寻找的答案是;

错误是您惯常使用的标准内容,例如,回显不存在的$ variable。
异常仅从PHP 5开始,并且在处理对象时才会出现。

为简单起见:

异常是您在处理对象时遇到的错误。通过try / catch语句,您可以对它们执行某些操作,其用法与if / else语句非常相似。尝试执行此操作,如果没有问题,请执行此操作。

如果您不“捕获”异常,那么它将变成标准错误。

错误是通常会暂停脚本的php基本错误。

Try / catch通常用于建立数据库连接(如PDO),如果您要重定向脚本或在连接不起作用时执行其他操作,则很好。但是,如果您只想显示错误消息并停止脚本,则不需要它,未捕获的异常会变成致命错误。或者,您也可以使用站点范围的错误处理设置。

希望能有所帮助


3
异常也可以与PHP中的过程代码一起使用。
Tiberiu-Ionuț Stan 2013年

2

在PHP 7.1和更高版本中,catch块可以使用竖线(|)字符指定多个异常。当对来自不同类层次结构的不同异常进行相同处理时,这很有用。

try {
  // do something
} catch (Error | Exception $e) {
  echo $e->getMessage();
}

1

异常是由代码使用throw,errors故意抛出的,不是那么多。

错误是由于某些通常无法处理的事情导致的。(IO错误,TCP / IP错误,空引用错误)


1
这不一定是真的。在许多情况下,会检查错误并有意地将返回码发送回去。实际上,每种非面向对象的语言都是如此。例外也就是规则的例外。在这两种情况下,都会出问题,引起注意并应进行处理。PHP文件上传是通过返回码进行故意错误处理的一个示例-php.net/manual/en/features.file-upload.errors.php
evan

1

我打算为您提供有关错误控制的最不寻常的讨论。

几年前,我在语言中构建了一个非常好的错误处理程序,尽管一些名称已更改,但今天的错误处理原理是相同的。我有一个定制的多任务操作系统,必须能够从所有级别的数据错误中恢复,并且不会出现内存泄漏,堆栈增长或崩溃的情况。因此,接下来就是我对错误和异常必须如何操作以及它们如何区别的理解。我只是说我对try catch的内部原理不了解,因此在某种程度上进行了猜测。

错误处理背后发生的第一件事是从一个程序状态跳到另一种程序状态。怎么做?我会解决的。

从历史上看,错误更老,更简单,异常更新,更复杂,更强大。错误可以正常工作,直到您需要将它们冒出来为止,这相当于将一个难题提交给主管。

错误可以是数字,例如错误号,有时也可以是一个或多个关联的字符串。例如,如果发生文件读取错误,则您可能可以报告该错误,并且可能无法正常运行。(是的,这比以前的崩溃要大。)

关于异常的常说是,异常是位于特殊异常堆栈上的对象。就像程序流的返回堆栈一样,但是它保留一个返回状态,仅用于错误尝试和捕获。(我以前把它们称为ePush和ePop,而?Abort是有条件的抛出,它将使ePop并恢复到该水平,而Abort则是完全死掉或退出。)

堆栈的最底部是有关初始调用程序的信息,该调用程序是知道外部尝试启动时(通常是程序启动时)状态的对象。最上一层,即堆栈的下一层,下一层是内部try / catch块的异常对象,上层是子层,下层是父层。

如果将尝试放入尝试中,则将内部尝试堆叠在外部尝试之上。当内部try中发生错误并且内部catch无法处理错误或将错误抛出到外部try时,控制权将传递给外部catch块(对象)以查看其是否可以处理错误,即您的主管。

因此,此错误堆栈的真正作用是能够标记并恢复程序流和系统状态,换句话说,它使程序不会崩溃返回堆栈,并在出现问题时将其他内容(数据)弄乱。因此,它还可以保存其他任何资源(例如内存分配池)的状态,因此可以在捕获完成后清理它们。通常,这可能是一件非常复杂的事情,这就是为什么异常处理通常很慢的原因。通常,这些异常块需要进入很多状态。

因此,try / catch块设置了一种状态,以便在其他所有情况混乱时可以返回到该状态。就像父母一样。当我们的生活陷入困境时,我们可以回到父母的腿上,他们会再次康复。

希望我没有让你失望。


1

您可以添加此评论

function doSomething()
{
   /** @noinspection PhpUnhandledExceptionInspection */
   throw new Exception();
}

0

定义set_error_handler()后,错误处理程序将与Exception的处理程序相似。参见下面的代码:

 <?php
 function handleErrors( $e_code ) {
   echo "error code: " . $e_code . "<br>";
 }

 set_error_handler( "handleErrors" ); 

 trigger_error( "trigger a fatal error", E_USER_ERROR);
 echo "after error."; //it would run if set_error_handler is defined, otherwise, it wouldn't show
?>
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.