PHP 异常及错误处理相关


下面介绍下PHP中关于异常及错误处理相关的内容


正文

在PHP 中将代码自身异常(一般是环境或者语法非法所致)称作错误 Error,将运行中出现的逻辑错误称为异常 Exception。

Error与Exception都继承自Throwable,而RuntimeException则继承自Exception。

下面是7.1.0-7.1.33的继承关系:

Throwable
    Error
         ArithmeticError
              DivisionByZeroError
         AssertionError
         ParseError
         TypeError
              ArgumentCountError
    Exception
         ClosedGeneratorException
         DOMException
         ErrorException
         IntlException
         LogicException
              BadFunctionCallException
                  BadMethodCallException
              DomainException
              InvalidArgumentException
              LengthException
              OutOfRangeException
         PharException
         ReflectionException
         RuntimeException
              OutOfBoundsException
              OverflowException
              PDOException
              RangeException
              UnderflowException
              UnexpectedValueException

其他版本的继承关系可以看这里

只有Throwable类型的实例才可以被抛出(throw)或捕获(catch),可以通过 try/catch 来处理。

try {
    throw new \RuntimeException("运行时错误");
} catch (\Throwable $e) {
    $msg = $e->getMessage();
}

Error 和 Exception 的异同

Exception 需要通过 throw new Exception 手动抛出;

Error 可以在 PHP 脚本执行发生错误时自动触发,也可以通过 trigger_errors() 手动触发;

都实现了 Throwable 接口,可以通过 catch (Throwable $t) {…} 同时捕获 Error 和 Exception;

如果不捕获并处理 Exception,程序会终止,并报出 Fatal Error 错误,但捕获后程序可以继续执行;

用 catch (Error $e) { … },或者通过注册错误处理函数( set_error_handler())来捕获 Error;

用 catch (Exception $e) { … } 或者通过注册异常处理函数( set_exception_handler())来捕获 Exception;

用 catch (Throwable $e) { … } 可以同时捕获 Exception 和 Error。

Error 类 和 Exception 类 都继承自 Throwable 接口,常用方法有:

getMessage — 获取异常消息内容
getCode — 获取异常代码
getFile — 导致异常的程序文件名称
getLine — 导致异常的行号

Throwable代码:

/**
 * Throwable is the base interface for any object that can be thrown via a throw statement in PHP 7,
 * including Error and Exception.
 * @link https://php.net/manual/en/class.throwable.php
 * @since 7.0
 */
interface Throwable
{
    /**
     * Gets the message
     * @link https://php.net/manual/en/throwable.getmessage.php
     * @return string
     * @since 7.0
     */
    public function getMessage();

    /**
     * Gets the exception code
     * @link https://php.net/manual/en/throwable.getcode.php
     * @return int <p>
     * Returns the exception code as integer in
     * {@see Exception} but possibly as other type in
     * {@see Exception} descendants (for example as
     * string in {@see PDOException}).
     * </p>
     * @since 7.0
     */
    public function getCode();

    /**
     * Gets the file in which the exception occurred
     * @link https://php.net/manual/en/throwable.getfile.php
     * @return string Returns the name of the file from which the object was thrown.
     * @since 7.0
     */
    public function getFile();

    /**
     * Gets the line on which the object was instantiated
     * @link https://php.net/manual/en/throwable.getline.php
     * @return int Returns the line number where the thrown object was instantiated.
     * @since 7.0
     */
    public function getLine();

    /**
     * Gets the stack trace
     * @link https://php.net/manual/en/throwable.gettrace.php
     * @return array <p>
     * Returns the stack trace as an array in the same format as
     * {@see debug_backtrace()}.
     * </p>
     * @since 7.0
     */
    public function getTrace();

    /**
     * Gets the stack trace as a string
     * @link https://php.net/manual/en/throwable.gettraceasstring.php
     * @return string Returns the stack trace as a string.
     * @since 7.0
     */
    public function getTraceAsString();

    /**
     * Returns the previous Throwable
     * @link https://php.net/manual/en/throwable.getprevious.php
     * @return Throwable Returns the previous {@see Throwable} if available, or <b>NULL</b> otherwise.
     * @since 7.0
     */
    public function getPrevious();

    /**
     * Gets a string representation of the thrown object
     * @link https://php.net/manual/en/throwable.tostring.php
     * @return string <p>Returns the string representation of the thrown object.</p>
     * @since 7.0
     */
    public function __toString();
}

Exception代码:

/**
 * Exception is the base class for
 * all Exceptions.
 * @link https://php.net/manual/en/class.exception.php
 */
class Exception implements Throwable {
    protected $message;
    protected $code;
    protected $file;
    protected $line;


    /**
     * Clone the exception
     * Tries to clone the Exception, which results in Fatal error.
     * @link https://php.net/manual/en/exception.clone.php
     * @return void
     * @since 5.1.0
     */
    final private function __clone() { }

    /**
     * Construct the exception. Note: The message is NOT binary safe.
     * @link https://php.net/manual/en/exception.construct.php
     * @param string $message [optional] The Exception message to throw.
     * @param int $code [optional] The Exception code.
     * @param Throwable $previous [optional] The previous throwable used for the exception chaining.
     * @since 5.1.0
     */
    public function __construct($message = "", $code = 0, Throwable $previous = null) { }

    /**
     * Gets the Exception message
     * @link https://php.net/manual/en/exception.getmessage.php
     * @return string the Exception message as a string.
     * @since 5.1.0
     */
    final public function getMessage() { }

    /**
     * Gets the Exception code
     * @link https://php.net/manual/en/exception.getcode.php
     * @return mixed|int the exception code as integer in
     * <b>Exception</b> but possibly as other type in
     * <b>Exception</b> descendants (for example as
     * string in <b>PDOException</b>).
     * @since 5.1.0
     */
    final public function getCode() { }

    /**
     * Gets the file in which the exception occurred
     * @link https://php.net/manual/en/exception.getfile.php
     * @return string the filename in which the exception was created.
     * @since 5.1.0
     */
    final public function getFile() { }

    /**
     * Gets the line in which the exception occurred
     * @link https://php.net/manual/en/exception.getline.php
     * @return int the line number where the exception was created.
     * @since 5.1.0
     */
    final public function getLine() { }

    /**
     * Gets the stack trace
     * @link https://php.net/manual/en/exception.gettrace.php
     * @return array the Exception stack trace as an array.
     * @since 5.1.0
     */
    final public function getTrace() { }

    /**
     * Returns previous Exception
     * @link https://php.net/manual/en/exception.getprevious.php
     * @return Exception the previous <b>Exception</b> if available
     * or null otherwise.
     * @since 5.3.0
     */
    final public function getPrevious() { }

    /**
     * Gets the stack trace as a string
     * @link https://php.net/manual/en/exception.gettraceasstring.php
     * @return string the Exception stack trace as a string.
     * @since 5.1.0
     */
    final public function getTraceAsString() { }

    /**
     * String representation of the exception
     * @link https://php.net/manual/en/exception.tostring.php
     * @return string the string representation of the exception.
     * @since 5.1.0
     */
    public function __toString() { }

    public function __wakeup() { }
}

Error代码:

/**
 * Error is the base class for all internal PHP error exceptions.
 * @link https://php.net/manual/en/class.error.php
 * @since 7.0
 */
class Error implements Throwable {

    /**
     * Construct the error object.
     * @link https://php.net/manual/en/error.construct.php
     * @param string $message [optional] The Error message to throw.
     * @param int $code [optional] The Error code.
     * @param Throwable $previous [optional] The previous throwable used for the exception chaining.
     */
    public function __construct($message = "", $code = 0, Throwable $previous = null)
    {
    }

    /***
     * Gets the message
     * @link https://php.net/manual/en/throwable.getmessage.php
     * @return string
     * @since 7.0
     */
    public final function getMessage()
    {
    }

    /**
     * Gets the exception code
     * @link https://php.net/manual/en/throwable.getcode.php
     * @return int <p>
     * Returns the exception code as integer in
     * {@see Exception} but possibly as other type in
     * {@see Exception} descendants (for example as
     * string in {@see PDOException}).
     * </p>
     * @since 7.0
     */
    public final function getCode(){}


    /**
     * Gets the file in which the exception occurred
     * @link https://php.net/manual/en/throwable.getfile.php
     * @return string Returns the name of the file from which the object was thrown.
     * @since 7.0
     */
    public final function getFile(){}


    /**
     * Gets the line on which the object was instantiated
     * @link https://php.net/manual/en/throwable.getline.php
     * @return int Returns the line number where the thrown object was instantiated.
     * @since 7.0
     */
    public final function getLine(){}


    /**
     * Gets the stack trace
     * @link https://php.net/manual/en/throwable.gettrace.php
     * @return array <p>
     * Returns the stack trace as an array in the same format as
     * {@see debug_backtrace()}.
     * </p>
     * @since 7.0
     */
    public final function getTrace(){}

    /**
     * Gets the stack trace as a string
     * @link https://php.net/manual/en/throwable.gettraceasstring.php
     * @return string Returns the stack trace as a string.
     * @since 7.0
     */
    public final function getTraceAsString(){}

    /**
     * Returns the previous Throwable
     * @link https://php.net/manual/en/throwable.getprevious.php
     * @return Throwable Returns the previous {@see Throwable} if available, or <b>NULL</b> otherwise.
     * @since 7.0
     */
    public final function getPrevious(){}
    /**
     * Gets a string representation of the thrown object
     * @link https://php.net/manual/en/throwable.tostring.php
     * @return string <p>Returns the string representation of the thrown object.</p>
     * @since 7.0
     */
    public function __toString(){}

    /**
     * Clone the error
     * Error can not be clone, so this method results in fatal error.
     * @return void
     * @link https://php.net/manual/en/error.clone.php
     */
    private final function __clone(){}

    public function __wakeup(){}
}

PHP错误

PHP报告错误是为了响应一些内部错误情况,而且通过类型来表示不同的内部错误情况,并且可以通过设置来显示或者记录下来。

PHP错误最佳实践

  • 一定要让 PHP 报告错误;
  • 在开发环境中要显示错误;
  • 在生产环境中不能显示错误;
  • 在开发和生产环境中都要记录错误。
PHP错误类型以及错误级别

PHP错误类型作为PHP核心之错误扩展中的预定义常量存在。

PHP分为多个错误级别,而且在不同版本中的错误级别有可能不一致,建议使用预定义常量,而不是数字。

PHP错误类型详表:

常量 说明
1 E_ERROR 致命的运行时错误,不可恢复,如位置异常,内存不足等。注意,如果有未被捕获的异常,也是会触发这个级别的。
2 E_WARNING 运行时警告 (非致命错误)。仅给出提示信息,但是脚本不会终止运行。
4 E_PARSE 解析错误,在解析PHP文件时产生,并强制PHP在执行前退出
8 E_NOTICE 运行时通知。表示脚本遇到可能会表现为错误的情况,但是在可以正常运行的脚本里面也可能会有类似的通知。
16 E_CODE_ERROR 在PHP初始化启动过程中发生的致命错误。该错误类似E_ERROR,但是是由PHP引擎核心产生的。
32 E_CODE_WARNING PHP初始化启动过程中发生的警告 (非致命错误) 。类似 E_WARNING,但是是由PHP引擎核心产生的。
64 E_COMPILE_ERROR 致命编译时错误。类似E_ERROR, 但是是由Zend脚本引擎产生的。
128 E_COMPILE_WARNING 编译时警告 (非致命错误)。类似E_WARNING,但是是由Zend脚本引擎产生的。
256 E_USER_ERROR 用户产生的错误信息。类似E_ERROR, 但是是由用户自己在代码中使用PHP函数 trigger_error()来产生的。
512 E_USER_WARNING 用户产生的警告信息。类似E_WARNING, 但是是由用户自己在代码中使用PHP函数trigger_error()来产生的。
1024 E_USER_NOTICE 用户产生的通知信息。类似E_NOTICE, 但是是由用户自己在代码中使用PHP函数trigger_error()来产生的。
2048 E_STRICT 启用 PHP 对代码的修改建议,以确保代码具有最佳的互操作性和向前兼容性。
4096 E_RECOVERABLE_ERROR 可被捕捉的致命错误。 它表示发生了一个可能非常危险的错误,但是还没有导致PHP引擎处于不稳定的状态。 如果该错误没有被用户自定义句柄捕获 (参见 set_error_handler()),将成为一个 E_ERROR 从而脚本会终止运行。
8192 E_DEPRECATED 运行时通知。启用后将会对在未来版本中可能无法正常工作的代码给出警告。
16384 E_USER_DEPRECATED 用户产生的警告信息。 类似E_DEPRECATED, 但是是由用户自己在代码中使用PHP函数 trigger_error()来产生的。
30719 E_ALL E_STRICT除外的所有错误和警告信息。PHP5.4后包含E_STRICT错误

PHP错误类型总结:

  • E_ERROR:运行时致命错误
  • E_WARNING:运行时警告
  • E_PARSE:解析时错误
  • E_NOTICE:运行时通知,可能为错误。
  • E_CODE_ERROR和E_CODE_WARNING:PHP初始化时产生的致命错误和警告
  • E_COMPIL_ERROR和E_COMPIL_WARNING:编译时产生的致命错误和警告
  • E_STRICT:你的代码可以运行,但是不是PHP建议的写法。
  • E_DEPRECATED:运行时通知,启用后将会对在未来版本中可能无法正常工作的代码给出警告。
  • E_RECOVERABLE_ERROR:可被捕获的E_ERROR,如果用户未定义 set_error_handler()函数则成为一个E_ERROR错误
  • E_USER_ERROR、E_USER_WARNING、E_USER_NOTICE和E_USER_DEPRECATED:用户自定义的错误,使用trigger_error()抛出。
  • E_ALL:除了E_STRICT以外的所有错误信息。PHP5.4后包含E_STRICT错误
PHP错误类型使用

可以在配置文件或者 error_reporting()函数中使用用于设置应该报告何种错误,可以使用按位运算符来组合这些值或者屏蔽某些类型的错误。 在配置文件中只能使用 | 、 & 、 ^ 、 ~ 、 ! 来进行组合。

php.ini配置文件控制
error_reporting = E_ALL          // 报告错误级别,什么级别的
display_errors = On             // 是否把错误展示在输出上,这个输出可能是output(PHP执行的输出流),也可能是stdout(为进程的标准输出)。PHP5.2以后可以设置值为 `stderr`,则可以将错误展示到 stderr输出(即标准错误输出)
display_startup_errors = On      // 是否把启动过程的错误信息显示在页面上
log_errors = On                 // 是否要记录错误日志
log_errors_max_len = 1024        // 错误日志的最大长度
ignore_repeated_errors = Off     // 是否忽略重复的错误
ignore_repeated_source = Off     // 忽略重复消息时,也忽略消息的来源
report_memleaks = On            // 如果这个参数设置为Off,则内存泄露信息不会显示 (在 stdout 或者日志中)。
track_errors = Off              // 是否使用全局变量$php_errormsg来记录最后一个错误
html_errors = On                // 是否把输出中的函数等信息变为HTML链接
xmlrpc_errors = 0               // 是否使用XML-RPC的错误信息格式记录错误
xmlrpc_error_number = 0          // 用作 XML-RPC faultCode 元素的值。
docref_root = http://manual/en/  // 如果打开了html_errors参数,PHP将会在出错信息上显示超连接,直接链接到一个说明这个错误或者导致这个错误的函数的页面。你可以从http://www.php.net/docs.php下载php手册,并设置docref_root参数,将他指向你本地的手册所在目录。你还必须设置"docref_ext"来指定文件的扩展名  
error_prepend_string = "11"      //错误信息之前输出的内容。 
error_append_string = "22"       //错误信息之后输出的内容。
fastcgi.logging = 0             // 是否把php错误抛出到fastcgi中
error_log = /tmp/php_errors.log  // php中的错误显示的日志位置
函数控制
  1. error_reporting:配置什么样子的错误能显示,这个是最重要的控制。error_reporting( int $level ):可以设置$level,不设置则输出当前的错误级别。
// 关闭所有PHP错误报告
error_reporting(0);

// Report simple running errors
error_reporting(E_ERROR | E_WARNING | E_PARSE);

// 报告 E_NOTICE也挺好 (报告未初始化的变量或者捕获变量名的错误拼写)
error_reporting(E_ERROR | E_WARNING | E_PARSE | E_NOTICE);

// 除了 E_NOTICE,报告其他所有错误
error_reporting(E_ALL ^ E_NOTICE);

// 报告所有 PHP 错误
error_reporting(E_ALL);

// 报告所有 PHP 错误
error_reporting(-1);
  1. display_errors:是否展示错误说明,设置为Off则不显示错误的说明信息。 如ini_set('display_errors', false);ini_set('display_errors', 'On');

  2. set_error_handler(callable $error_handler , int $error_types = E_ALL ` E_STRICT ):设置用户自定义的错误处理函数。
    • 其中$error_handle为一个回调函数
    • $_error_type:如果没有该掩码, 无论 error_reporting 是如何设置的, error_handler 都会在每个错误发生时被调用。
    • 以下是不能被用户自定义函数调用的错误级别:E_ERROR、 E_PARSE、 E_CORE_ERROR、 E_CORE_WARNING、 E_COMPILE_ERROR、 E_COMPILE_WARNING
    • error_types 里指定的错误类型都会绕过 PHP 标准错误处理程序, 除非回调函数返回了 FALSE
    • 如果错误发生在脚本执行之前(比如文件上传时),将不会 调用自定义的错误处理程序因为它尚未在那时注册。
  3. bool trigger_error ( string $error_msg[, int $error_type = E_USER_NOTICE ):产生一个用户级别的错误
PHP7错误处理机制

PHP 7 改变了大多数错误的报告方式。不同于传统(PHP 5)的错误报告机制,现在大多数错误被作为 Error 异常抛出。

这种 Error 异常可以像 Exception 异常一样被第一个匹配的 try / catch 块所捕获。如果没有匹配的 catch 块, 则调用异常处理函数(事先通过 set_exception_handler()注册)进行处理。 如果尚未注册异常处理函数, 则按照传统方式处理:被报告为一个致命错误(Fatal Error)。

Error 类并非继承自 Exception 类,所以不能用 catch (Exception $e) { … } 来捕获 Error。你可以用 catch (Error $e) { … }, 或者通过注册异常处理函数(set_error_handler())来捕获 Error。

PHP异常

PHP的异常和其他语言中的异常一样,使用 try、catch、finally,只不过PHP的异常系统不会自动抛出,需要手动抛出。

手动抛出,就要用到set_exception_handler()函数。

set_exception_handler(callable $exception_handler):设置默认的异常处理程序,用于没有用 try/catch 块来捕获的异常。 在 exception_handler 调用后异常会中止。

$exception_handler回调函数的参数是Exception类,PHP7以后为Throwable类,该类可以捕获大多数的错误

自定义错误处理举例

在项目开发中,一直没留意过错误处理这一块的内容,现在因为开发需要, 翻阅了下PHP中关于异常及错误处理相关的内容,手边使用的框架是Yii2,就用Yii2说明一下。

在配置文件中有:

'components' => [
    'errorHandler' => [
        'class' => 'yii\web\ErrorHandler',
        'errorAction' => 'site/error',
    ],
    ...
]

yii/web/Application本身就是服务定位器,在加载配置文件时,把errorHandler组件配置好。

yii/base/Application中有:

public function __construct($config = [])
{
    Yii::$app = $this;
    static::setInstance($this);

    $this->state = self::STATE_BEGIN;

    $this->preInit($config);

    $this->registerErrorHandler($config);

    Component::__construct($config);
}

protected function registerErrorHandler(&$config)
{
    if (YII_ENABLE_ERROR_HANDLER) {
        if (!isset($config['components']['errorHandler']['class'])) {
            echo "Error: no errorHandler component is configured.\n";
            exit(1);
        }
        $this->set('errorHandler', $config['components']['errorHandler']);
        unset($config['components']['errorHandler']);
        $this->getErrorHandler()->register();
    }
}

我们可以追溯一下Yii2关于错误处理的类,最后会发现在yii\base\ErrorHandler.php中有:

public function register()
{
    ini_set('display_errors', false);
    set_exception_handler([$this, 'handleException']);
    if (defined('HHVM_VERSION')) {
        set_error_handler([$this, 'handleHhvmError']);
    } else {
        set_error_handler([$this, 'handleError']);
    }
    if ($this->memoryReserveSize > 0) {
        $this->_memoryReserve = str_repeat('x', $this->memoryReserveSize);
    }
    register_shutdown_function([$this, 'handleFatalError']);
}

public function unregister()
{
    restore_error_handler();
    restore_exception_handler();
}

这里把PHP关于异常错误的函数基本都写出来了:

ini_set('display_errors', false); // 页面不显示错误

可以用error_reporting()设置哪些类型的错误被纪录。

set_exception_handler() // 设置用户自定义的异常处理函数

set_error_handler() // 设置用户自定义的错误处理函数

register_shutdown_function() // 设置用户自定义的PHP程序执行完成后(包括致命错误导致的程序执行完成)执行的函数

restore_exception_handle() // 恢复之前定义过的异常处理函数

restore_error_handler() // 还原之前的错误处理函数

yii\base\ErrorHandler.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`.
 *
 * For more details and usage information on ErrorHandler, see the [guide article on handling errors](guide:runtime-handling-errors).
 *
 * @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 bool whether to discard any existing page output before error display. Defaults to true.
     */
    public $discardExistingOutput = true;
    /**
     * @var int 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|null the exception that is being handled currently.
     */
    public $exception;

    /**
     * @var string Used to reserve memory for fatal error handler.
     */
    private $_memoryReserve;
    /**
     * @var \Exception from HHVM error that stores backtrace
     */
    private $_hhvmException;


    /**
     * Register this error handler.
     */
    public function register()
    {
        ini_set('display_errors', false);
        set_exception_handler([$this, 'handleException']);
        if (defined('HHVM_VERSION')) {
            set_error_handler([$this, 'handleHhvmError']);
        } else {
            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();

        // set preventive HTTP status code to 500 in case error handling somehow fails and headers are sent
        // HTTP exceptions will override this value in renderException()
        if (PHP_SAPI !== 'cli') {
            http_response_code(500);
        }

        try {
            $this->logException($exception);
            if ($this->discardExistingOutput) {
                $this->clearOutput();
            }
            $this->renderException($exception);
            if (!YII_ENV_TEST) {
                \Yii::getLogger()->flush(true);
                if (defined('HHVM_VERSION')) {
                    flush();
                }
                exit(1);
            }
        } catch (\Exception $e) {
            // an other exception could be thrown while displaying the exception
            $this->handleFallbackExceptionMessage($e, $exception);
        } catch (\Throwable $e) {
            // additional check for \Throwable introduced in PHP 7
            $this->handleFallbackExceptionMessage($e, $exception);
        }

        $this->exception = null;
    }

    /**
     * Handles exception thrown during exception processing in [[handleException()]].
     * @param \Exception|\Throwable $exception Exception that was thrown during main exception processing.
     * @param \Exception $previousException Main exception processed in [[handleException()]].
     * @since 2.0.11
     */
    protected function handleFallbackExceptionMessage($exception, $previousException)
    {
        $msg = "An Error occurred while handling another error:\n";
        $msg .= (string) $exception;
        $msg .= "\nPrevious exception:\n";
        $msg .= (string) $previousException;
        if (YII_DEBUG) {
            if (PHP_SAPI === 'cli') {
                echo $msg . "\n";
            } else {
                echo '<pre>' . htmlspecialchars($msg, ENT_QUOTES, Yii::$app->charset) . '</pre>';
            }
        } else {
            echo 'An internal server error occurred.';
        }
        $msg .= "\n\$_SERVER = " . VarDumper::export($_SERVER);
        error_log($msg);
        if (defined('HHVM_VERSION')) {
            flush();
        }
        exit(1);
    }

    /**
     * Handles HHVM execution errors such as warnings and notices.
     *
     * This method is used as a HHVM error handler. It will store exception that will
     * be used in fatal error handler
     *
     * @param int $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 int $line the line number the error was raised at.
     * @param mixed $context
     * @param mixed $backtrace trace of error
     * @return bool whether the normal error handler continues.
     *
     * @throws ErrorException
     * @since 2.0.6
     */
    public function handleHhvmError($code, $message, $file, $line, $context, $backtrace)
    {
        if ($this->handleError($code, $message, $file, $line)) {
            return true;
        }
        if (E_ERROR & $code) {
            $exception = new ErrorException($message, $code, $code, $file, $line);
            $ref = new \ReflectionProperty('\Exception', 'trace');
            $ref->setAccessible(true);
            $ref->setValue($exception, $backtrace);
            $this->_hhvmException = $exception;
        }

        return false;
    }

    /**
     * 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 int $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 int $line the line number the error was raised at.
     * @return bool 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);
                    if (defined('HHVM_VERSION')) {
                        flush();
                    }
                    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)) {
            if (!empty($this->_hhvmException)) {
                $exception = $this->_hhvmException;
            } else {
                $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);
            if (defined('HHVM_VERSION')) {
                flush();
            }
            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
     * @since 2.0.3 this method is now public.
     */
    public 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($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|\Error $exception the exception being converted
     * @return string the string representation of the exception.
     */
    public static function convertExceptionToString($exception)
    {
        if ($exception instanceof UserException) {
            return "{$exception->getName()}: {$exception->getMessage()}";
        }

        if (YII_DEBUG) {
            return static::convertExceptionToVerboseString($exception);
        }

        return 'An internal server error occurred.';
    }

    /**
     * Converts an exception into a string that has verbose information about the exception and its trace.
     * @param \Exception|\Error $exception the exception being converted
     * @return string the string representation of the exception.
     *
     * @since 2.0.14
     */
    public static function convertExceptionToVerboseString($exception)
    {
        if ($exception instanceof Exception) {
            $message = "Exception ({$exception->getName()})";
        } elseif ($exception instanceof ErrorException) {
            $message = (string)$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();

        return $message;
    }
}

yii\base\ErrorException.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;

/**
 * ErrorException represents a PHP error.
 *
 * For more details and usage information on ErrorException, see the [guide article on handling errors](guide:runtime-handling-errors).
 *
 * @author Alexander Makarov <sam@rmcreative.ru>
 * @since 2.0
 */
class ErrorException extends \ErrorException
{
    /**
     * This constant represents a fatal error in the HHVM engine.
     *
     * PHP Zend runtime won't call the error handler on fatals, HHVM will, with an error code of 16777217
     * We will handle fatal error a bit different on HHVM.
     * @see https://github.com/facebook/hhvm/blob/master/hphp/runtime/base/runtime-error.h#L62
     * @since 2.0.6
     */
    const E_HHVM_FATAL_ERROR = 16777217; // E_ERROR | (1 << 24)


    /**
     * Constructs the exception.
     * @link http://php.net/manual/en/errorexception.construct.php
     * @param $message [optional]
     * @param $code [optional]
     * @param $severity [optional]
     * @param $filename [optional]
     * @param $lineno [optional]
     * @param $previous [optional]
     */
    public function __construct($message = '', $code = 0, $severity = 1, $filename = __FILE__, $lineno = __LINE__, \Exception $previous = null)
    {
        parent::__construct($message, $code, $severity, $filename, $lineno, $previous);

        if (function_exists('xdebug_get_function_stack')) {
            // XDebug trace can't be modified and used directly with PHP 7
            // @see https://github.com/yiisoft/yii2/pull/11723
            $xDebugTrace = array_slice(array_reverse(xdebug_get_function_stack()), 3, -1);
            $trace = [];
            foreach ($xDebugTrace as $frame) {
                if (!isset($frame['function'])) {
                    $frame['function'] = 'unknown';
                }

                // XDebug < 2.1.1: http://bugs.xdebug.org/view.php?id=695
                if (!isset($frame['type']) || $frame['type'] === 'static') {
                    $frame['type'] = '::';
                } elseif ($frame['type'] === 'dynamic') {
                    $frame['type'] = '->';
                }

                // XDebug has a different key name
                if (isset($frame['params']) && !isset($frame['args'])) {
                    $frame['args'] = $frame['params'];
                }
                $trace[] = $frame;
            }

            $ref = new \ReflectionProperty('Exception', 'trace');
            $ref->setAccessible(true);
            $ref->setValue($this, $trace);
        }
    }

    /**
     * Returns if error is one of fatal type.
     *
     * @param array $error error got from error_get_last()
     * @return bool if error is one of fatal type
     */
    public static function isFatalError($error)
    {
        return isset($error['type']) && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING, self::E_HHVM_FATAL_ERROR]);
    }

    /**
     * @return string the user-friendly name of this exception
     */
    public function getName()
    {
        static $names = [
            E_COMPILE_ERROR => 'PHP Compile Error',
            E_COMPILE_WARNING => 'PHP Compile Warning',
            E_CORE_ERROR => 'PHP Core Error',
            E_CORE_WARNING => 'PHP Core Warning',
            E_DEPRECATED => 'PHP Deprecated Warning',
            E_ERROR => 'PHP Fatal Error',
            E_NOTICE => 'PHP Notice',
            E_PARSE => 'PHP Parse Error',
            E_RECOVERABLE_ERROR => 'PHP Recoverable Error',
            E_STRICT => 'PHP Strict Warning',
            E_USER_DEPRECATED => 'PHP User Deprecated Warning',
            E_USER_ERROR => 'PHP User Error',
            E_USER_NOTICE => 'PHP User Notice',
            E_USER_WARNING => 'PHP User Warning',
            E_WARNING => 'PHP Warning',
            self::E_HHVM_FATAL_ERROR => 'HHVM Fatal Error',
        ];

        return isset($names[$this->getCode()]) ? $names[$this->getCode()] : 'Error';
    }
}






参考资料

ini_set http://www.php.net/manual/en/function.ini-set.php

error_reporting http://www.php.net/manual/en/function.error-reporting.php

set_exception_handler http://php.net/manual/zh/function.set-exception-handler.php

set_error_handler http://php.net/manual/zh/function.set-error-handler.php

register_shutdown_function http://www.php.net/manual/en/function.register-shutdown-function.php

restore_exception_handle http://php.net/manual/zh/function.restore-exception-handler.php

restore_error_handler http://php.net/manual/zh/function.restore-error-handler.php

错误处理 函数 http://php.net/manual/zh/ref.errorfunc.php

PHP常见概念混淆(八)之错误与异常 https://www.cnblogs.com/qiye5757/p/9530919.html

PHP 优雅的捕获处理错误 – E_PARSE / E_ERROR https://segmentfault.com/a/1190000014926703

PHP7中的异常与错误处理 https://novnan.github.io/PHP/throwable-exceptions-and-errors-in-php7/

PHP 手册 语言参考 Errors Basics https://www.php.net/manual/zh/language.errors.basics.php

PHP的错误机制总结 https://www.cnblogs.com/yjf512/p/5314345.html

php 的错误和异常处理机制 https://juejin.im/entry/5987d2ff6fb9a03c314fe732

我们什么时候应该使用异常? http://www.laruence.com/2012/02/02/2515.html

PHP 最佳实践之异常和错误 https://learnku.com/articles/5435/exceptions-and-errors-in-php-best-practices

PHP 中 Error 和 Exception 两种异常的特性及日志记录或显示 https://www.cnblogs.com/kika/p/10851557.html


返回