How do I catch a PHP fatal (`E_ERROR`) error?

前端 未结 17 2345
北荒
北荒 2020-11-21 06:21

I can use set_error_handler() to catch most PHP errors, but it doesn\'t work for fatal (E_ERROR) errors, such as calling a function that doesn\'t e

17条回答
  •  我在风中等你
    2020-11-21 06:46

    Nice solution found in 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);
        }
    }
    

    This class allows you to start the specific ErrorHandler sometimes if you need it. And then you can also stop the Handler.

    Use this class e.g. like this:

    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;
    

    Link to the full class code:
    https://github.com/zendframework/zf2/blob/master/library/Zend/Stdlib/ErrorHandler.php


    A maybe better solution is that one from Monolog:

    Link to the full class code:
    https://github.com/Seldaek/monolog/blob/master/src/Monolog/ErrorHandler.php

    It can also handle FATAL_ERRORS using the register_shutdown_function function. According to this class a FATAL_ERROR is one of the following array(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;
        }
    
        // [...]
    }
    

提交回复
热议问题