我可以使用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()的任何需要。

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

其他回答

下面是一个获取当前error_handler method =)的小技巧

<?php
    register_shutdown_function('__fatalHandler');

    function __fatalHandler()
    {
        $error = error_get_last();

        // Check if it's a core/fatal error. Otherwise, it's a normal shutdown
        if($error !== NULL && $error['type'] === E_ERROR) {

            // It is a bit hackish, but the set_exception_handler
            // will return the old handler
            function fakeHandler() { }

            $handler = set_exception_handler('fakeHandler');
            restore_exception_handler();
            if($handler !== null) {
                call_user_func(
                    $handler,
                    new ErrorException(
                        $error['message'],
                        $error['type'],
                        0,
                        $error['file'],
                        $error['line']));
            }
            exit;
        }
    }
?>

我还想提醒你,如果你打电话来

<?php
    ini_set('display_errors', false);
?>

PHP停止显示错误。否则,错误文本将先于错误处理程序发送给客户端。

我开发这个函数是为了使“沙盒”代码能够导致致命错误。由于从闭包register_shutdown_function抛出的异常不会从预致命错误调用堆栈中触发,因此我被迫在此函数之后退出,以提供统一的使用方法。

function superTryCatchFinallyAndExit( Closure $try, Closure $catch = NULL, Closure $finally )
{
    $finished = FALSE;
    register_shutdown_function( function() use ( &$finished, $catch, $finally ) {
        if( ! $finished ) {
            $finished = TRUE;
            print "EXPLODE!".PHP_EOL;
            if( $catch ) {
                superTryCatchFinallyAndExit( function() use ( $catch ) {
                    $catch( new Exception( "Fatal Error!!!" ) );
                }, NULL, $finally );                
            } else {
                $finally();                
            }
        }
    } );
    try {
        $try();
    } catch( Exception $e ) {
        if( $catch ) {
            try {
                $catch( $e );
            } catch( Exception $e ) {}
        }
    }
    $finished = TRUE;
    $finally();
    exit();
}

您不能捕获/处理致命错误,但可以记录/报告它们。 为了快速调试,我修改了这段简单代码的一个答案

function __fatalHandler()
{
    $error = error_get_last();

    // Check if it's a core/fatal error, otherwise it's a normal shutdown
    if ($error !== NULL && in_array($error['type'],
        array(E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING,
              E_COMPILE_ERROR, E_COMPILE_WARNING,E_RECOVERABLE_ERROR))) {

        echo "<pre>fatal error:\n";
        print_r($error);
        echo "</pre>";
        die;
    }
}

register_shutdown_function('__fatalHandler');

从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()的任何需要。

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

在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;
    }

    // [...]
}