IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> PHP知识库 -> ?(^_-) Yii2框架源码解析之错误和异常处理 -> 正文阅读

[PHP知识库]?(^_-) Yii2框架源码解析之错误和异常处理

前言

所有的框架的错误处理机制,都在整个框架运行的顺序中排在前列,一般错误处理机制排在常量定义、配置加载、类的自动加载之后,排在其他流程逻辑之前。

base\Application

错误处理的入口是应用类的基类base\Application的构造方法中实现

    public function __construct($config = [])
    {
      
        // 当application基类被继承之后,Yii::$app就变成了继承子类。
        Yii::$app = $this;

        // 将当前应用注册到$this->loadedModules
        static::setInstance($this);

        // 表示应用已开始
        $this->state = self::STATE_BEGIN;
        // 下面的配置数组传引用
        // 初始化准备
        $this->preInit($config);

        // 注册错误处理
        $this->registerErrorHandler($config);

        // 组件初始化
        Component::__construct($config);
    }


    public function preInit(&$config)
    {
        // 配置中id 和 basePath 必传, 表示应用的id和应用的部署根路径
        if (!isset($config['id'])) {
            throw new InvalidConfigException('The "id" configuration for the Application is required.');
        }
        if (isset($config['basePath'])) {
            $this->setBasePath($config['basePath']);
            unset($config['basePath']);
        } else {
            throw new InvalidConfigException('The "basePath" configuration for the Application is required.');
        }

        // 设置一些路径
        if (isset($config['vendorPath'])) {
            $this->setVendorPath($config['vendorPath']);
            unset($config['vendorPath']);
        } else {
            // set "@vendor"
            $this->getVendorPath();
        }
        if (isset($config['runtimePath'])) {
            $this->setRuntimePath($config['runtimePath']);
            unset($config['runtimePath']);
        } else {
            // set "@runtime"
            $this->getRuntimePath();
        }

        // 设置时区
        if (isset($config['timeZone'])) {
            $this->setTimeZone($config['timeZone']);
            unset($config['timeZone']);
        } elseif (!ini_get('date.timezone')) {
            $this->setTimeZone('UTC');
        }

        // 如果存在容器的配置,就设置容器的初始属性
        if (isset($config['container'])) {
            $this->setContainer($config['container']);
            unset($config['container']);
        }

        // merge core components with custom components
        // 合并核心组件的初始配置
        foreach ($this->coreComponents() as $id => $component) {
            if (!isset($config['components'][$id])) {
                $config['components'][$id] = $component;
            } elseif (is_array($config['components'][$id]) && !isset($config['components'][$id]['class'])) {
                $config['components'][$id]['class'] = $component['class'];
            }
        }
    }

我们可以看到,在错误注册之前,做了一些对$config的一些初始化的准备操作。就是将$config里面的某些配置项,赋值到当前应用的具体属性。并且,在Component没有初始化之前,$config在preInit()和registerErrorHandler()方法中是引用传值,表示$config可以在方法体中被修改。我们接下来看下错误处理的方法registerErrorHandler()

registerErrorHandler()

    protected function registerErrorHandler(&$config)
    {
        // 注册php错误处理

        // 开启了错误处理,则进行错误处理
        if (YII_ENABLE_ERROR_HANDLER) {
            // 配置组件中,如果没有错误处理类,那么就直接挂
            // 注意,错误处理errorHandler的默认核心类在web\Application也就是子类里面,将获取核心组件配置覆盖写,并merge了。
            if (!isset($config['components']['errorHandler']['class'])) {
                echo "Error: no errorHandler component is configured.\n";
                exit(1);
            }

            // 将错误处理类,注册到全局的组件树上。
            $this->set('errorHandler', $config['components']['errorHandler']);

            // 组件可以全局获取了。配置中没有必要存在了。避免Component初始化的时候,设置属性报错。
            unset($config['components']['errorHandler']);

            // 真正的错误处理机制
            $this->getErrorHandler()->register();
        }
    }

可以看到,registerErrorHandler()方法只是将错误异常处理类加载到全局组件components上。真正执行错误处理的方法再ErrorHandler.php这个类里面。这种方式很好的实现了程序的解耦。

ErrorHandler.php

    public function register()
    {
        // 错误和异常的注册
        // 单例,避免重复执行
        if (!$this->_registered) {

            // 关闭错误显示,设置异常处理的方法
            ini_set('display_errors', false);
            set_exception_handler([$this, 'handleException']);

            // 使用 HHVM_VERSION 判断是否已经定义,存在代表是当前运行环境是虚拟机环境。分别设置错误处理方法。
            if (defined('HHVM_VERSION')) {
                set_error_handler([$this, 'handleHhvmError']);
            } else {
                set_error_handler([$this, 'handleError']);
            }

            // 如果保留了内存(用于处理内存溢出的致命错误)
            if ($this->memoryReserveSize > 0) {
                // 将"x"重复写$this->memoryReserveSize次,保留起来。(即保留的内存)
                $this->_memoryReserve = str_repeat('x', $this->memoryReserveSize);
            }

            // 程序终止,处理函数
            register_shutdown_function([$this, 'handleFatalError']);
            $this->_registered = true;
        }
    }
    

    public function handleException($exception)
    {
        // 异常处理的方法。使用前得先注册。通过php的异常处理机制来实现。

        // 如果应用程序正常终止,则不做处理。
        if ($exception instanceof ExitException) {
            return;
        }

        $this->exception = $exception;

        // 处理异常的时候,先禁用错误的捕获,避免出现递归。
        $this->unregister();

        // 设置http状态码
        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) {
            $this->handleFallbackExceptionMessage($e, $exception);
        } catch (\Throwable $e) {
            $this->handleFallbackExceptionMessage($e, $exception);
        }
        // 异常处理完了,就置空。
        $this->exception = null;
    }
    
    public function unregister()
    {
        // 恢复php的错误处理和异常处理。即取消了当前错误类的初始化(注册)。
        if ($this->_registered) {
            restore_error_handler();
            restore_exception_handler();
            $this->_registered = false;
        }
    }

    public function handleError($code, $message, $file, $line)
    {
        if (error_reporting() & $code) {
            // 错误类手动导入,避免自动加载发生错误的时候,导致错误处理机制无法正常运行。
            if (!class_exists('yii\\base\\ErrorException', false)) {
                require_once __DIR__ . '/ErrorException.php';
            }
            $exception = new ErrorException($message, $code, $code, $file, $line);

            if (PHP_VERSION_ID < 70400) {
                // 在 PHP 7.4 之前,不能在 __toString() 内部抛出异常 - 它会导致致命错误
                $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;
    }

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

可以看到register()方法中,使用了函数:set_exception_handler()、set_error_handler()、register_shutdown_function(),分别注册了异常处理,错误处理,程序终止处理。

  PHP知识库 最新文章
Laravel 下实现 Google 2fa 验证
UUCTF WP
DASCTF10月 web
XAMPP任意命令执行提升权限漏洞(CVE-2020-
[GYCTF2020]Easyphp
iwebsec靶场 代码执行关卡通关笔记
多个线程同步执行,多个线程依次执行,多个
php 没事记录下常用方法 (TP5.1)
php之jwt
2021-09-18
上一篇文章      下一篇文章      查看所有文章
加:2021-09-08 10:26:21  更:2021-09-08 10:28:01 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 1:27:55-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码