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

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


当前回答

从PHP 7.4.13开始,我的经验是程序中所有可能的错误和异常都可以用两个回调函数来捕获:

set_error_handler("ErrorCB");
set_exception_handler("ExceptCB");

ErrorCB只是以任何想要的方式报告其参数,并调用Exit()。

ExceptCB在其异常参数上调用“get”方法,并执行一些逻辑来确定文件、行和函数的位置(如果您想要详细信息,请询问我),并以任何所需的方式报告信息并返回。

try/catch的唯一需要是当@或isset()不够用时,你需要抑制某些代码的错误。对“main函数”使用try/catch而不设置处理程序会失败,因为它不能捕获所有错误。

如果有人发现代码产生了这个方法无法捕捉到的错误,请告诉我,我会编辑这个答案。这种方法不能拦截的一个错误是靠近PHP程序末尾的单个{字符;这将生成一个Parse错误,这要求您通过包含错误处理的Include文件运行主PHP程序。

我没有发现register_shutdown_function()的任何需要。

注意,我所关心的是报告错误,然后退出程序;我不需要从错误中恢复——这确实是一个更难的问题。

其他回答

我刚刚想出了这个解决方案(PHP 5.2.0+):

function shutDownFunction() {
    $error = error_get_last();
     // Fatal error, E_ERROR === 1
    if ($error['type'] === E_ERROR) {
         // Do your stuff
    }
}
register_shutdown_function('shutDownFunction');

不同的错误类型在预定义常量中定义。

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

在某些情况下,即使是致命的错误也应该被捕获(你可能需要在优雅地退出之前做一些清理工作,而不是直接死亡..)。

我在我的CodeIgniter应用程序中实现了一个pre_system钩子,这样我就可以通过电子邮件得到我的致命错误,这帮助我找到没有报告的错误(或在修复后报告的错误,因为我已经知道了它们:))。

Sendemail检查错误是否已经报告,这样它就不会多次向您发送已知错误的垃圾邮件。

class PHPFatalError {

    public function setHandler() {
        register_shutdown_function('handleShutdown');
    }
}

function handleShutdown() {
    if (($error = error_get_last())) {
        ob_start();
        echo "<pre>";
        var_dump($error);
        echo "</pre>";
        $message = ob_get_clean();
        sendEmail($message);
        ob_start();
        echo '{"status":"error","message":"Internal application error!"}';
        ob_flush();
        exit();
    }
}

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.

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

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

我开发了一种方法来捕获PHP中的所有错误类型(几乎所有)!我不确定E_CORE_ERROR(我认为不会仅适用于该错误)!但是,对于其他致命错误(E_ERROR, E_PARSE, E_COMPILE…),只使用一个错误处理函数就可以正常工作!这就是我的解决方案:

把下面的代码放在你的主文件(index.php)上:

<?php
    define('E_FATAL',  E_ERROR | E_USER_ERROR | E_PARSE | E_CORE_ERROR |
            E_COMPILE_ERROR | E_RECOVERABLE_ERROR);

    define('ENV', 'dev');

    // Custom error handling vars
    define('DISPLAY_ERRORS', TRUE);
    define('ERROR_REPORTING', E_ALL | E_STRICT);
    define('LOG_ERRORS', TRUE);

    register_shutdown_function('shut');

    set_error_handler('handler');

    // Function to catch no user error handler function errors...
    function shut(){

        $error = error_get_last();

        if($error && ($error['type'] & E_FATAL)){
            handler($error['type'], $error['message'], $error['file'], $error['line']);
        }

    }

    function handler( $errno, $errstr, $errfile, $errline ) {

        switch ($errno){

            case E_ERROR: // 1 //
                $typestr = 'E_ERROR'; break;
            case E_WARNING: // 2 //
                $typestr = 'E_WARNING'; break;
            case E_PARSE: // 4 //
                $typestr = 'E_PARSE'; break;
            case E_NOTICE: // 8 //
                $typestr = 'E_NOTICE'; break;
            case E_CORE_ERROR: // 16 //
                $typestr = 'E_CORE_ERROR'; break;
            case E_CORE_WARNING: // 32 //
                $typestr = 'E_CORE_WARNING'; break;
            case E_COMPILE_ERROR: // 64 //
                $typestr = 'E_COMPILE_ERROR'; break;
            case E_CORE_WARNING: // 128 //
                $typestr = 'E_COMPILE_WARNING'; break;
            case E_USER_ERROR: // 256 //
                $typestr = 'E_USER_ERROR'; break;
            case E_USER_WARNING: // 512 //
                $typestr = 'E_USER_WARNING'; break;
            case E_USER_NOTICE: // 1024 //
                $typestr = 'E_USER_NOTICE'; break;
            case E_STRICT: // 2048 //
                $typestr = 'E_STRICT'; break;
            case E_RECOVERABLE_ERROR: // 4096 //
                $typestr = 'E_RECOVERABLE_ERROR'; break;
            case E_DEPRECATED: // 8192 //
                $typestr = 'E_DEPRECATED'; break;
            case E_USER_DEPRECATED: // 16384 //
                $typestr = 'E_USER_DEPRECATED'; break;
        }

        $message =
            '<b>' . $typestr .
            ': </b>' . $errstr .
            ' in <b>' . $errfile .
            '</b> on line <b>' . $errline .
            '</b><br/>';

        if(($errno & E_FATAL) && ENV === 'production'){

            header('Location: 500.html');
            header('Status: 500 Internal Server Error');

        }

        if(!($errno & ERROR_REPORTING))
            return;

        if(DISPLAY_ERRORS)
            printf('%s', $message);

        //Logging error on php file error log...
        if(LOG_ERRORS)
            error_log(strip_tags($message), 0);
    }

    ob_start();

    @include 'content.php';

    ob_end_flush();
?>