我可以使用set_error_handler()来捕获大多数PHP错误,但它不适用于致命(E_ERROR)错误,例如调用不存在的函数。是否有其他方法来捕捉这些错误?

我试图调用mail()为所有错误和运行PHP 5.2.3。


当前回答

PHP有可捕获的致命错误。它们被定义为E_RECOVERABLE_ERROR。PHP手册将E_RECOVERABLE_ERROR描述为:

可捕捉的致命错误。它表示可能发生了危险的错误,但没有使引擎处于不稳定状态。如果用户定义的句柄没有捕捉到错误(参见set_error_handler()),应用程序将中止,因为它是一个E_ERROR。

您可以通过使用set_error_handler()并检查E_RECOVERABLE_ERROR来“捕获”这些“致命”错误。我发现当这个错误被捕获时抛出一个异常很有用,然后你可以使用try/catch。

这个问题和答案提供了一个有用的例子:如何在PHP类型提示中捕获“可捕获的致命错误”?

然而,E_ERROR错误可以处理,但不能恢复,因为引擎处于不稳定状态。

其他回答

如果使用PHP, >= 5.1.0 只需对errrexception类执行如下操作:

<?php
    // Define an error handler
    function exception_error_handler($errno, $errstr, $errfile, $errline ) {
        throw new ErrorException($errstr, $errno, 0, $errfile, $errline);
    }

    // Set your error handler
    set_error_handler("exception_error_handler");

    /* Trigger exception */
    try
    {
        // Try to do something like finding the end of the internet
    }
    catch(ErrorException $e)
    {
        // Anything you want to do with $e
    }
?>

在Zend Framework 2中找到了很好的解决方案:

/**
 * ErrorHandler that can be used to catch internal PHP errors
 * and convert to an ErrorException instance.
 */
abstract class ErrorHandler
{
    /**
     * Active stack
     *
     * @var array
     */
    protected static $stack = array();

    /**
     * Check if this error handler is active
     *
     * @return bool
     */
    public static function started()
    {
        return (bool) static::getNestedLevel();
    }

    /**
     * Get the current nested level
     *
     * @return int
     */
    public static function getNestedLevel()
    {
        return count(static::$stack);
    }

    /**
     * Starting the error handler
     *
     * @param int $errorLevel
     */
    public static function start($errorLevel = \E_WARNING)
    {
        if (!static::$stack) {
            set_error_handler(array(get_called_class(), 'addError'), $errorLevel);
        }

        static::$stack[] = null;
    }

    /**
     * Stopping the error handler
     *
     * @param  bool $throw Throw the ErrorException if any
     * @return null|ErrorException
     * @throws ErrorException If an error has been catched and $throw is true
     */
    public static function stop($throw = false)
    {
        $errorException = null;

        if (static::$stack) {
            $errorException = array_pop(static::$stack);

            if (!static::$stack) {
                restore_error_handler();
            }

            if ($errorException && $throw) {
                throw $errorException;
            }
        }

        return $errorException;
    }

    /**
     * Stop all active handler
     *
     * @return void
     */
    public static function clean()
    {
        if (static::$stack) {
            restore_error_handler();
        }

        static::$stack = array();
    }

    /**
     * Add an error to the stack
     *
     * @param int    $errno
     * @param string $errstr
     * @param string $errfile
     * @param int    $errline
     * @return void
     */
    public static function addError($errno, $errstr = '', $errfile = '', $errline = 0)
    {
        $stack = & static::$stack[count(static::$stack) - 1];
        $stack = new ErrorException($errstr, 0, $errno, $errfile, $errline, $stack);
    }
}

这个类允许您在需要时启动特定的ErrorHandler。然后你也可以停止处理程序。

使用这个类,例如:

ErrorHandler::start(E_WARNING);
$return = call_function_raises_E_WARNING();

if ($innerException = ErrorHandler::stop()) {
    throw new Exception('Special Exception Text', 0, $innerException);
}

// or
ErrorHandler::stop(true); // directly throws an Exception;

链接到完整的类代码:https://github.com/zendframework/zf2/blob/master/library/Zend/Stdlib/ErrorHandler.php

一个更好的解决方案是独白: 链接到完整的类代码:https://github.com/Seldaek/monolog/blob/master/src/Monolog/ErrorHandler.php

它还可以使用register_shutdown_function函数处理FATAL_ERRORS。根据这个类,FATAL_ERROR是以下数组之一(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR)。

class ErrorHandler
{
    // [...]

    public function registerExceptionHandler($level = null, $callPrevious = true)
    {
        $prev = set_exception_handler(array($this, 'handleException'));
        $this->uncaughtExceptionLevel = $level;
        if ($callPrevious && $prev) {
            $this->previousExceptionHandler = $prev;
        }
    }

    public function registerErrorHandler(array $levelMap = array(), $callPrevious = true, $errorTypes = -1)
    {
        $prev = set_error_handler(array($this, 'handleError'), $errorTypes);
        $this->errorLevelMap = array_replace($this->defaultErrorLevelMap(), $levelMap);
        if ($callPrevious) {
            $this->previousErrorHandler = $prev ?: true;
        }
    }

    public function registerFatalHandler($level = null, $reservedMemorySize = 20)
    {
        register_shutdown_function(array($this, 'handleFatalError'));

        $this->reservedMemory = str_repeat(' ', 1024 * $reservedMemorySize);
        $this->fatalLevel = $level;
    }

    // [...]
}

不是真的。之所以叫致命错误,是因为它们是致命的。你无法从中恢复过来。

好吧,似乎可以用其他方式捕获致命错误:)

ob_start('fatal_error_handler');

function fatal_error_handler($buffer){
    $error = error_get_last();
    if($error['type'] == 1){
        // Type, message, file, line
        $newBuffer='<html><header><title>Fatal Error </title></header>
                      <style>
                    .error_content{
                        background: ghostwhite;
                        vertical-align: middle;
                        margin:0 auto;
                        padding: 10px;
                        width: 50%;
                     }
                     .error_content label{color: red;font-family: Georgia;font-size: 16pt;font-style: italic;}
                     .error_content ul li{ background: none repeat scroll 0 0 FloralWhite;
                                border: 1px solid AliceBlue;
                                display: block;
                                font-family: monospace;
                                padding: 2%;
                                text-align: left;
                      }
                      </style>
                      <body style="text-align: center;">
                        <div class="error_content">
                             <label >Fatal Error </label>
                             <ul>
                               <li><b>Line</b> ' . $error['line'] . '</li>
                               <li><b>Message</b> ' . $error['message'] . '</li>
                               <li><b>File</b> ' . $error['file'] . '</li>
                             </ul>

                             <a href="javascript:history.back()"> Back </a>
                        </div>
                      </body></html>';

        return $newBuffer;
    }
    return $buffer;
}

PHP没有提供常规的方法来捕捉和恢复致命错误。这是因为在发生致命错误后通常不应恢复处理。字符串匹配输出缓冲区(正如PHP.net上描述的技术的原始帖子所建议的那样)绝对是不明智的。这是不可靠的。

从错误处理程序方法中调用mail()函数也有问题。如果你有很多错误,你的邮件服务器就会有大量的工作,你就会发现自己有一个杂乱的收件箱。为了避免这种情况,可以考虑运行cron定期扫描错误日志并相应地发送通知。您可能还想研究一下系统监控软件,比如Nagios。


关于注册一个shutdown函数:

的确,您可以注册一个shutdown函数,这是一个很好的答案。

这里的重点是,我们通常不应该试图从致命错误中恢复,特别是不应该对输出缓冲区使用正则表达式。我是在回复被接受的答案,它链接到php.net上的一个建议,这个建议已经被更改或删除了。

该建议是在异常处理期间对输出缓冲区使用正则表达式,并且在出现致命错误的情况下(通过匹配您可能期望的任何配置错误文本检测到),尝试进行某种恢复或继续处理。这不是一个推荐的做法(我相信这就是为什么我也找不到最初的建议。不是我忽略了它,就是php社区否定了它)。

It might be worth noting that the more recent versions of PHP (around 5.1) seem to call the shutdown function earlier, before the output buffering callback is envoked. In version 5 and earlier, that order was the reverse (the output buffering callback was followed by the shutdown function). Also, since about 5.0.5 (which is much earlier than the questioner's version 5.2.3), objects are unloaded well before a registered shutdown function is called, so you won't be able to rely on your in-memory objects to do much of anything.

因此,注册一个关闭函数是可以的,但是应该由关闭函数执行的任务可能仅限于少数温和的关闭过程。

这里的关键是给那些偶然遇到这个问题并在最初接受的答案中看到建议的人的一些智慧之语。不要正则化你的输出缓冲区。