mirror of
				https://github.com/yiisoft/yii2.git
				synced 2025-11-04 14:46:19 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			262 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			262 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
/**
 | 
						|
 * @link http://www.yiiframework.com/
 | 
						|
 * @copyright Copyright (c) 2008 Yii Software LLC
 | 
						|
 * @license http://www.yiiframework.com/license/
 | 
						|
 */
 | 
						|
 | 
						|
namespace yii\base;
 | 
						|
 | 
						|
use Yii;
 | 
						|
use yii\helpers\VarDumper;
 | 
						|
use yii\web\HttpException;
 | 
						|
 | 
						|
/**
 | 
						|
 * ErrorHandler handles uncaught PHP errors and exceptions.
 | 
						|
 *
 | 
						|
 * ErrorHandler is configured as an application component in [[\yii\base\Application]] by default.
 | 
						|
 * You can access that instance via `Yii::$app->errorHandler`.
 | 
						|
 *
 | 
						|
 * @author Qiang Xue <qiang.xue@gmail.com>
 | 
						|
 * @author Alexander Makarov <sam@rmcreative.ru>
 | 
						|
 * @author Carsten Brandt <mail@cebe.cc>
 | 
						|
 * @since 2.0
 | 
						|
 */
 | 
						|
abstract class ErrorHandler extends Component
 | 
						|
{
 | 
						|
    /**
 | 
						|
     * @var boolean whether to discard any existing page output before error display. Defaults to true.
 | 
						|
     */
 | 
						|
    public $discardExistingOutput = true;
 | 
						|
    /**
 | 
						|
     * @var integer the size of the reserved memory. A portion of memory is pre-allocated so that
 | 
						|
     * when an out-of-memory issue occurs, the error handler is able to handle the error with
 | 
						|
     * the help of this reserved memory. If you set this value to be 0, no memory will be reserved.
 | 
						|
     * Defaults to 256KB.
 | 
						|
     */
 | 
						|
    public $memoryReserveSize = 262144;
 | 
						|
    /**
 | 
						|
     * @var \Exception the exception that is being handled currently.
 | 
						|
     */
 | 
						|
    public $exception;
 | 
						|
 | 
						|
    /**
 | 
						|
     * @var string Used to reserve memory for fatal error handler.
 | 
						|
     */
 | 
						|
    private $_memoryReserve;
 | 
						|
 | 
						|
 | 
						|
    /**
 | 
						|
     * Register this error handler
 | 
						|
     */
 | 
						|
    public function register()
 | 
						|
    {
 | 
						|
        ini_set('display_errors', false);
 | 
						|
        set_exception_handler([$this, 'handleException']);
 | 
						|
        set_error_handler([$this, 'handleError']);
 | 
						|
        if ($this->memoryReserveSize > 0) {
 | 
						|
            $this->_memoryReserve = str_repeat('x', $this->memoryReserveSize);
 | 
						|
        }
 | 
						|
        register_shutdown_function([$this, 'handleFatalError']);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Unregisters this error handler by restoring the PHP error and exception handlers.
 | 
						|
     */
 | 
						|
    public function unregister()
 | 
						|
    {
 | 
						|
        restore_error_handler();
 | 
						|
        restore_exception_handler();
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Handles uncaught PHP exceptions.
 | 
						|
     *
 | 
						|
     * This method is implemented as a PHP exception handler.
 | 
						|
     *
 | 
						|
     * @param \Exception $exception the exception that is not caught
 | 
						|
     */
 | 
						|
    public function handleException($exception)
 | 
						|
    {
 | 
						|
        if ($exception instanceof ExitException) {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        $this->exception = $exception;
 | 
						|
 | 
						|
        // disable error capturing to avoid recursive errors while handling exceptions
 | 
						|
        $this->unregister();
 | 
						|
 | 
						|
        try {
 | 
						|
            $this->logException($exception);
 | 
						|
            if ($this->discardExistingOutput) {
 | 
						|
                $this->clearOutput();
 | 
						|
            }
 | 
						|
            $this->renderException($exception);
 | 
						|
            if (!YII_ENV_TEST) {
 | 
						|
                exit(1);
 | 
						|
            }
 | 
						|
        } catch (\Exception $e) {
 | 
						|
            // an other exception could be thrown while displaying the exception
 | 
						|
            $msg = (string) $e;
 | 
						|
            $msg .= "\nPrevious exception:\n";
 | 
						|
            $msg .= (string) $exception;
 | 
						|
            if (YII_DEBUG) {
 | 
						|
                if (PHP_SAPI === 'cli') {
 | 
						|
                    echo $msg . "\n";
 | 
						|
                } else {
 | 
						|
                    echo '<pre>' . htmlspecialchars($msg, ENT_QUOTES, Yii::$app->charset) . '</pre>';
 | 
						|
                }
 | 
						|
            }
 | 
						|
            $msg .= "\n\$_SERVER = " . VarDumper::export($_SERVER);
 | 
						|
            error_log($msg);
 | 
						|
            exit(1);
 | 
						|
        }
 | 
						|
 | 
						|
        $this->exception = null;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Handles PHP execution errors such as warnings and notices.
 | 
						|
     *
 | 
						|
     * This method is used as a PHP error handler. It will simply raise an [[ErrorException]].
 | 
						|
     *
 | 
						|
     * @param integer $code the level of the error raised.
 | 
						|
     * @param string $message the error message.
 | 
						|
     * @param string $file the filename that the error was raised in.
 | 
						|
     * @param integer $line the line number the error was raised at.
 | 
						|
     * @return boolean whether the normal error handler continues.
 | 
						|
     *
 | 
						|
     * @throws ErrorException
 | 
						|
     */
 | 
						|
    public function handleError($code, $message, $file, $line)
 | 
						|
    {
 | 
						|
        if (error_reporting() & $code) {
 | 
						|
            // load ErrorException manually here because autoloading them will not work
 | 
						|
            // when error occurs while autoloading a class
 | 
						|
            if (!class_exists('yii\\base\\ErrorException', false)) {
 | 
						|
                require_once(__DIR__ . '/ErrorException.php');
 | 
						|
            }
 | 
						|
            $exception = new ErrorException($message, $code, $code, $file, $line);
 | 
						|
 | 
						|
            // in case error appeared in __toString method we can't throw any exception
 | 
						|
            $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
 | 
						|
            array_shift($trace);
 | 
						|
            foreach ($trace as $frame) {
 | 
						|
                if ($frame['function'] == '__toString') {
 | 
						|
                    $this->handleException($exception);
 | 
						|
                    exit(1);
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            throw $exception;
 | 
						|
        }
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Handles fatal PHP errors
 | 
						|
     */
 | 
						|
    public function handleFatalError()
 | 
						|
    {
 | 
						|
        unset($this->_memoryReserve);
 | 
						|
 | 
						|
        // load ErrorException manually here because autoloading them will not work
 | 
						|
        // when error occurs while autoloading a class
 | 
						|
        if (!class_exists('yii\\base\\ErrorException', false)) {
 | 
						|
            require_once(__DIR__ . '/ErrorException.php');
 | 
						|
        }
 | 
						|
 | 
						|
        $error = error_get_last();
 | 
						|
 | 
						|
        if (ErrorException::isFatalError($error)) {
 | 
						|
            $exception = new ErrorException($error['message'], $error['type'], $error['type'], $error['file'], $error['line']);
 | 
						|
            $this->exception = $exception;
 | 
						|
 | 
						|
            $this->logException($exception);
 | 
						|
 | 
						|
            if ($this->discardExistingOutput) {
 | 
						|
                $this->clearOutput();
 | 
						|
            }
 | 
						|
            $this->renderException($exception);
 | 
						|
 | 
						|
            // need to explicitly flush logs because exit() next will terminate the app immediately
 | 
						|
            Yii::getLogger()->flush(true);
 | 
						|
 | 
						|
            exit(1);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Renders the exception.
 | 
						|
     * @param \Exception $exception the exception to be rendered.
 | 
						|
     */
 | 
						|
    abstract protected function renderException($exception);
 | 
						|
 | 
						|
    /**
 | 
						|
     * Logs the given exception
 | 
						|
     * @param \Exception $exception the exception to be logged
 | 
						|
     */
 | 
						|
    protected function logException($exception)
 | 
						|
    {
 | 
						|
        $category = get_class($exception);
 | 
						|
        if ($exception instanceof HttpException) {
 | 
						|
            $category = 'yii\\web\\HttpException:' . $exception->statusCode;
 | 
						|
        } elseif ($exception instanceof \ErrorException) {
 | 
						|
            $category .= ':' . $exception->getSeverity();
 | 
						|
        }
 | 
						|
        Yii::error((string) $exception, $category);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Removes all output echoed before calling this method.
 | 
						|
     */
 | 
						|
    public function clearOutput()
 | 
						|
    {
 | 
						|
        // the following manual level counting is to deal with zlib.output_compression set to On
 | 
						|
        for ($level = ob_get_level(); $level > 0; --$level) {
 | 
						|
            if (!@ob_end_clean()) {
 | 
						|
                ob_clean();
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Converts an exception into a PHP error.
 | 
						|
     *
 | 
						|
     * This method can be used to convert exceptions inside of methods like `__toString()`
 | 
						|
     * to PHP errors because exceptions cannot be thrown inside of them.
 | 
						|
     * @param \Exception $exception the exception to convert to a PHP error.
 | 
						|
     */
 | 
						|
    public static function convertExceptionToError($exception)
 | 
						|
    {
 | 
						|
        trigger_error(static::convertExceptionToString($exception), E_USER_ERROR);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Converts an exception into a simple string.
 | 
						|
     * @param \Exception $exception the exception being converted
 | 
						|
     * @return string the string representation of the exception.
 | 
						|
     */
 | 
						|
    public static function convertExceptionToString($exception)
 | 
						|
    {
 | 
						|
        if ($exception instanceof Exception && ($exception instanceof UserException || !YII_DEBUG)) {
 | 
						|
            $message = "{$exception->getName()}: {$exception->getMessage()}";
 | 
						|
        } elseif (YII_DEBUG) {
 | 
						|
            if ($exception instanceof Exception) {
 | 
						|
                $message = "Exception ({$exception->getName()})";
 | 
						|
            } elseif ($exception instanceof ErrorException) {
 | 
						|
                $message = "{$exception->getName()}";
 | 
						|
            } else {
 | 
						|
                $message = 'Exception';
 | 
						|
            }
 | 
						|
            $message .= " '" . get_class($exception) . "' with message '{$exception->getMessage()}' \n\nin "
 | 
						|
                . $exception->getFile() . ':' . $exception->getLine() . "\n\n"
 | 
						|
                . "Stack trace:\n" . $exception->getTraceAsString();
 | 
						|
        } else {
 | 
						|
            $message = 'Error: ' . $exception->getMessage();
 | 
						|
        }
 | 
						|
        return $message;
 | 
						|
    }
 | 
						|
}
 |