PHP:异常与错误?

Posted

技术标签:

【中文标题】PHP:异常与错误?【英文标题】:PHP: exceptions vs errors? 【发布时间】:2010-10-24 21:34:18 【问题描述】:

也许我在 php 手册中的某处遗漏了它,但错误和异常之间究竟有什么区别?我能看到的唯一区别是错误和异常的处理方式不同。但是是什么导致了异常,又是什么导致了错误呢?

【问题讨论】:

【参考方案1】:

例外是thrown - 它们旨在被捕获。错误通常是不可恢复的。例如,您有一段代码可以将一行插入数据库。此调用可能会失败(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";

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

【讨论】:

Errors are generally unrecoverable E_ERROR 和 E_PARSE 是两个最常见的不可恢复的错误(还有其他几个),但您将在 dev 中看到的绝大多数错误都是可恢复的(E_NOTICEE_WARNING 等)。不幸的是,PHP 的错误处理完全是一团糟——各种事情都会不必要地触发错误(例如,绝大多数文件系统函数)。一般来说,异常是“OOP 方式”,但不幸的是,一些 PHP 的原生 OOP API 使用错误而不是异常 :-( @DaveRandom E_NOTICE, E_WARNING 根据定义不是“错误”吗?我一直认为它们是 PHP 显示的“消息”,以通知程序员他/她编写的代码可能有问题。 @slhsen 这个问题确实是蹩脚的术语,这些消息的所有形式都通过 PHP 中的“错误处理系统”,语义上所有这些事件都是“错误”,即使语义上的通知/警告是在这种情况下,绝对与“错误”不同。值得庆幸的是,即将到来的 PHP7 至少通过将这些东西中的大部分转换为可捕获的异常(通过新的Throwable 接口)为整理这些混乱铺平了道路,提供了一种更具表现力和绝对的方式来区分和正确处理实际问题和咨询信息 我认为OP的意思更多是关于Error的后代与Exception的后代之间的区别。 从 PHP 7 开始,ErrorException 类都实现了 Throwable 接口并且可以被捕获。【参考方案2】:

我通常将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 类是从哪里来的,它的目的是什么? “set_exception_handler('global_exception_handler');”下面的所有内容只是演示,你不需要它,它只是为了展示在正常的非异常错误情况下会发生什么。 标准 PHP 专门定义了 ErrorException 以从一般错误处理程序中抛出。你能允许我编辑和更新你的帖子吗? @Tiberiu-IonuțStan:当然,但工作示例将不同步。另外,现在我可能会将人们从private-void.com 指向github.com/theredhead/red.web/blob/master/src/lib/bootstrap.php。【参考方案3】:

答案值得一提的是房间里的大象

错误是在运行时处理错误情况的旧方法。通常,代码会在执行某些代码之前调用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 的制造者实现了异常处理。但很可能支持旧代码,它们保留了错误处理并提供了一种使错误处理看起来像异常处理的方法。除此之外,不能保证某些代码不会重置错误处理程序,而这正是异常处理旨在提供的。

最终答案

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

【讨论】:

这才是异常和错误并存的真正原因。如果从头开始设计,php 应该只包含一个或另一个。 这是我认为最好的答案,因为它是最详细和解释性的。【参考方案4】:

这里要补充一点是关于处理异常和错误。对于应用程序开发人员而言,错误和异常都是您想要记录的“坏事”,以了解您的应用程序存在的问题——以便您的客户从长远来看有更好的体验。

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

【讨论】:

【参考方案5】:

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

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

请注意error_reporting() 检查以保持@ 运算符正常工作。此外,不需要定义自定义异常,PHP 有一个很好的类。

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

【讨论】:

【参考方案6】:

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

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

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

所以,如果它绝对,肯定需要在它开始之前完成执行 - 好吧,你不走运,因为时间旅行不存在。如果没有时间旅行,返回码是最快的选择。

编辑:

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

【讨论】:

当然,但是由于抛出异常而损失的周期数量远远超过了通过异常获得的额外描述能力。您可以抛出特定类型的异常,甚至可以向异常添加数据以包含错误代码。我也严重怀疑您的 10,000 * 索赔。即使您对时间差异的看法是正确的,与执行的代码相比,在任何现实世界场景中执行 return & if 与 new Execption、throw、catch 所花费的时间都可能如此微不足道,以至于这绝对是一个过早的优化。抛出异常,它们在 90% 的时间里都能更好地处理。 1. 10,000 倍是准确的 - 根据语言和编译器选项存在一些差异。 2. 您不必返回 null/false。您可以在此处返回一个数字 - 最多 MAX_ULONG 返回代码。您也可以返回一个失败字符串,然后只检查一个成功字符串或 int 或 null。 3. 在现实世界的场景中,每个时钟周期都很重要。 Facebook 拥有 5.52 亿日活跃用户。假设异常只有 2 倍,并且检查用户/通行证需要 0.001 秒,这意味着每天可以节省 153 小时的处理时间。在 10,000 倍时,它可以节省 175 年。仅用于检查登录尝试 - 每天。 @evan:仅供参考,他们在这里测试了带有异常的代码,但似乎并没有变慢:***.com/a/445094/260080 @MarcoDemaio 该问题仅涉及 try/catch 块而不会引发异常。更好的测试是在 noexcept() 中返回一个值并在 except() 中抛出异常。此外,它应该通过多种功能冒泡。 ***.com/a/104375/505172 声明 PHP 的差异实际上是 54 倍。我进行了自己的实时测试,它似乎慢了 2-10 倍。这一切都比预期的要好。 @evan:那时我不会担心,我只使用异常来跟踪意外/不可恢复的错误,所以即使它会慢 100 倍我也不会在意。我担心的是通过简单地添加 try/catch 块来使代码变慢。【参考方案7】:

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

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

为了简单起见:

异常是您在处理对象时遇到的错误。 try/catch 语句可以让你对它们做一些事情,并且使用起来很像 if/else 语句。 尝试这样做,如果有问题,不要紧,这样做。

如果你没有“捕获”异常,那么它就会变成标准错误。

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

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

希望有帮助

【讨论】:

异常也可以与 PHP 中的过程代码一起使用。【参考方案8】:

来自PHP: Exceptions - Manual:

从 PHP 7.1.0 开始,catch 块可以使用竖线 (|) 字符指定多个异常。这在处理来自不同类层次结构的不同异常时很有用。

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

【讨论】:

【参考方案9】:

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

错误是由于通常未处理的事情而产生的。 (IO 错误、TCP/IP 错误、空引用错误)

【讨论】:

这不一定是真的。在许多情况下,会检查错误并在适当的时候有意发回返回代码。事实上,每一种非面向对象的语言都是如此。例外也就是规则的例外。在这两种情况下,都会出现问题,注意到并且应该处理。 PHP 文件上传是通过返回码进行故意错误处理的一个示例 - php.net/manual/en/features.file-upload.errors.php【参考方案10】:

我打算给你一个关于错误控制的最不寻常的讨论。

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

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

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

错误可以是数字,例如错误编号,有时也可以是一个或多个相关字符串。例如,如果发生文件读取错误,您可能能够报告它是什么,并且可能会优雅地失败。 (嘿,这比过去的崩溃更上一层楼了。)

关于异常不常说的是异常是分层在特殊异常堆栈上的对象。它就像程序流的返回堆栈,但它只为错误尝试和捕获保持返回状态。 (我曾经称它们为 ePush 和 ePop,而 ?Abort 是一个有条件的 throw,它会 ePop 并恢复到那个水平,而 Abort 是一个完全死亡或退出。)

堆栈的底部是有关初始调用者的信息,该对象知道外部尝试启动时的状态,通常是在您的程序启动时。最重要的是,或者堆栈的下一层,up 是孩子,down 是父母,是下一个内部 try/catch 块的异常对象。

如果你把一个 try 放在一个 try 里面,你就是把内部 try 堆叠在外部 try 之上。当内部 try 发生错误并且内部 catch 无法处理或者错误被抛出到外部 try 时,则将控制权传递给外部 catch 块(对象)以查看它是否可以处理错误,即你的主管。

所以这个错误堆栈真正做的是能够标记和恢复程序流程和系统状态,换句话说,它允许程序在事情发生时不会崩溃返回堆栈并为其他人(数据)搞砸事情错误的。因此它还保存了任何其他资源(如内存分配池)的状态,因此它可以在捕获完成时清理它们。一般来说,这可能是一件非常复杂的事情,这就是异常处理通常很慢的原因。一般来说,这些异常块需要有相当多的状态。

因此,try/catch 块会设置一个状态,以便在其他所有事情都搞砸时能够返回。这就像一个父母。当我们的生活一团糟时,我们可以重新回到父母的怀抱中,他们会再次好起来的。

希望我没有让你失望。

【讨论】:

【参考方案11】:

一旦定义了 set_error_handler(),错误处理程序就类似于异常处理程序。见以下代码:

 <?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
?>

【讨论】:

【参考方案12】:

您可以添加此评论

function doSomething()

   /** @noinspection PhpUnhandledExceptionInspection */
   throw new Exception();

【讨论】:

以上是关于PHP:异常与错误?的主要内容,如果未能解决你的问题,请参考以下文章

PHP 异常与错误 —— RuntimeException

一起搞懂PHP的错误和异常

PHP 错误与异常的日志记录

2018/05/02 PHP 之错误与异常处理

php捕获Fatal error错误与异常处理

PHP 异常与错误 —— Throwable