1、载入框架的命名空间

在一个MVC框架中,命名空间分组管理是一个非常重要的设计,
它可以让我们从日常繁琐的require语句中解脱出来,也不必在担心require会重复引入的问题。
而且命名空间的映射表还可以帮我们优化一些层级非常深的类,不需要编写很长的目录结构,就能use到对应的类。
我们先来看下新的目录结构:

  1. ThinkMIMI WEB部署目录(或者子目录)
  2. ├─index.php 单一入口文件
  3. ├─application 应用目录
  4. └─《autoload_class_map.php 项目可自定义扩展命名空间映射表
  5. └─thinkmimi 框架核心文件目录
  6. ├─《library 框架核心类文件目录
  7. └─《Loader.php 命名空间类
  8. └─base.php 框架核心核心文件

下面我们先来修改/ThinkMIMI/thinkmimi/base.php文件的代码,
将我们之前测试用的echo THINK_PATH;语句删掉,然后再末尾写入以下代码:

  1. # 载入命名空间
  2. require LIB_PATH . 'Loader.php';
  3. # 注册自动加载
  4. \mimi\Loader::register();

下面我们再新建/ThinkMIMI/thinkmimi/libaray/Loader.php文件,写入以下代码:

  1. <?php
  2. namespace mimi;
  3. class Loader {
  4. /**
  5. * 命名空间-自定义路径映射表
  6. */
  7. public static $vendorMap = [
  8. 'app' => APP_PATH . APP_LICATION,
  9. 'mimi' => THINK_PATH . 'library',
  10. ];
  11. /**
  12. * 注册自动加载机制
  13. * @return void
  14. */
  15. public static function register() {
  16. # 注册系统自动加载
  17. spl_autoload_register('\mimi\Loader::autoload', true, true);
  18. # 更新映射表
  19. self::mergeMap();
  20. # 下面可以继续做 Composer 自动加载支持
  21. }
  22. /**
  23. * 自动加载器
  24. * @param string $class 自动传入的命名空间路径
  25. */
  26. public static function autoload($class) {
  27. # 获得解析后的命名空间绝对路径
  28. $file = self::findFile($class);
  29. if (file_exists($file)) {
  30. # 调用引入
  31. self::includeFile($file);
  32. }
  33. }
  34. /**
  35. * 解析命名空间对应的文件路径
  36. * @param string $class 命名空间路径
  37. * @return string 标准路径
  38. */
  39. private static function findFile($class) {
  40. $vendor = substr($class, 0, strpos($class, '\\')); // 顶级命名空间
  41. # 取出文件基目录
  42. if (!empty(self::$vendorMap[$vendor])) {
  43. $vendorDir = self::$vendorMap[$vendor];
  44. } else {
  45. $vendorDir = __DIR__ . DS . $vendor;
  46. }
  47. $filePath = substr($class, strlen($vendor)) . EXT; // 文件相对路径
  48. return strtr($vendorDir . $filePath, '\\', DS); // 文件标准路径
  49. }
  50. /**
  51. * 框架映射表 与 扩展映射表合并
  52. */
  53. private static function mergeMap() {
  54. $file = APP_PATH . APP_LICATION . DS . 'autoload_class_map' . EXT;
  55. if (file_exists($file)) {
  56. $map = require_once $file;
  57. self::$vendorMap = array_merge(self::$vendorMap, $map);
  58. }
  59. }
  60. /**
  61. * 引入文件
  62. */
  63. private static function includeFile($file) {
  64. require $file;
  65. }
  66. }

最后,我们再新建/ThinkMIMI/application/autoload_class_map.php文件,写入以下代码:

  1. <?php
  2. return [
  3. // 顶级命名空间 => 真实顶级路径
  4. // 'app' => APP_PATH . 'application',
  5. ];

到这里我们自定义MVC框架的命名空间与自动加载就完成了,
这时候我们再访问index.php文件,不会有任何输出,但只要不报错即可。

2、配置文件设计与加载

在实际开发中,有可能使用者会经常改动框架的一些配置文件,例如路由使用模式、默认分组目录等。
还有可能会需要自定义一些不属于框架本身的配置项,
同时,我们框架内部,还可能会存在一些不允许被使用者修改的配置项等。
这时候我们设计配置文件管理时,就需要将其分为三层结构:

  1. 1、框架内部配置文件
  2. 2、项目公共配置文件
  3. 3、项目分组独立配置文件

再编写一个Config类,将这3层配置文件合并后,再进行日常的增删改查。
下面我们来看下新的目录结构:

  1. ThinkMIMI WEB部署目录(或者子目录)
  2. ├─index.php 单一入口文件
  3. ├─application 应用目录
  4. ├─《index 默认项目分组目录
  5. └─《config.php 分组私有配置文件
  6. ├─《config.php 项目公共配置文件
  7. └─autoload_class_map.php 项目可自定义扩展命名空间映射表
  8. └─thinkmimi 框架核心文件目录
  9. ├─library 框架核心类文件目录
  10. ├─《Config.php 配置文件操作类
  11. └─Loader.php 命名空间类
  12. ├─《config.php 框架内部配置文件
  13. └─base.php 框架核心核心文件

下面我们先来修改/ThinkMIMI/thinkmimi/base.php文件的代码,在末尾写入以下代码:

  1. # 初始化框架配置项
  2. \mimi\Config::run(THINK_PATH.'config'.EXT);

下面我们再新建/ThinkMIMI/thinkmimi/libaray/Config.php文件,写入以下代码:

  1. <?php
  2. namespace mimi;
  3. class Config {
  4. /**
  5. * 配置项
  6. */
  7. private static $_CONFIG = [];
  8. /**
  9. * 初始化框架应用配置参数
  10. * @param string $file 框架核心配置路径
  11. */
  12. public static function run($file) {
  13. if (file_exists($file)) {
  14. self::$_CONFIG = require_once $file;
  15. }
  16. }
  17. /**
  18. * 导入配置项合并,只支持最多二维数组
  19. * @param array 需要导入的配置项
  20. * @param string $k 二维数组的键名
  21. */
  22. public static function load($data, $k=null) {
  23. foreach ($data as $key=>$val) {
  24. if ( is_array($val) ) {
  25. self::load($val, $key);
  26. } else {
  27. if (!empty($k)) {
  28. self::$_CONFIG[$k][$key] = $val;
  29. } else {
  30. self::$_CONFIG[$key] = $val;
  31. }
  32. }
  33. }
  34. }
  35. /**
  36. * 获取配置参数
  37. * @param string $key 配置键名,为空读取所有
  38. * @param void
  39. */
  40. public static function get($key=null) {
  41. if ($key===null) { return self::$_CONFIG; }
  42. $data = explode('.', $key);
  43. $param_A = $data[0];
  44. # 二维参数读取
  45. if (!empty($data[1])) {
  46. $param_B = $data[1];
  47. if (isset(self::$_CONFIG[$param_A][$param_B])) {
  48. return self::$_CONFIG[$param_A][$param_B];
  49. }
  50. }
  51. # 一维参数读取
  52. if (isset(self::$_CONFIG[$param_A])) {
  53. return self::$_CONFIG[$param_A];
  54. }
  55. return false;
  56. }
  57. /**
  58. * 设置配置参数
  59. * @param string $key 配置键名
  60. * @param string $val 配置对应的值
  61. * @param bool
  62. */
  63. public static function set($key, $val) {
  64. $data = explode('.', $key);
  65. $param_A = $data[0];
  66. # 二维参数修改
  67. if (!empty($data[1])) {
  68. $param_B = $data[1];
  69. self::$_CONFIG[$param_A][$param_B] = $val;
  70. return true;
  71. }
  72. # 一维参数修改
  73. if (isset(self::$_CONFIG[$param_A])) {
  74. self::$_CONFIG[$param_A] = $val;
  75. return true;
  76. }
  77. return false;
  78. }
  79. /**
  80. * 判断配置项是否存在
  81. * @param string $key 配置键名
  82. * @param bool
  83. */
  84. public static function has($key) {
  85. $data = explode('.', $key);
  86. $param_A = $data[0];
  87. # 二维参数读取
  88. if (!empty($data[1])) {
  89. $param_B = $data[1];
  90. if (!isset(self::$_CONFIG[$param_A][$param_B])) {
  91. return false;
  92. }
  93. }
  94. # 一维参数读取
  95. if (!isset(self::$_CONFIG[$param_A])) {
  96. return false;
  97. }
  98. return true;
  99. }
  100. }

然后我们再新建/ThinkMIMI/thinkmimi/config.php框架内部配置文件,写入以下代码:

  1. <?php
  2. return [
  3. // +------------------------------------------------------------------
  4. // | 应用设置
  5. // +------------------------------------------------------------------
  6. // 默认时区
  7. 'default_timezone' => 'PRC',
  8. // 默认全局过滤方法 用逗号分隔多个
  9. 'default_filter' => '',
  10. // 默认语言
  11. 'default_lang' => 'zh',
  12. // +----------------------------------------------------------------------
  13. // | 模板设置
  14. // +----------------------------------------------------------------------
  15. 'template' => [
  16. // DEBUG开启时的错误页面解析模板
  17. 'tpl_error_yes' => THINK_PATH . 'tpl' . DS . 'error_test.php',
  18. // DEBUG关闭时的错误页面解析模板
  19. 'tpl_error_no' => THINK_PATH . 'tpl' . DS . 'error_formal.php',
  20. // 错误跳转模板
  21. 'tpl_error' => THINK_PATH . 'tpl' . DS . 'error.php',
  22. // 警告跳转模板
  23. 'tpl_notice' => THINK_PATH . 'tpl' . DS . 'notice.php',
  24. // 正确跳转模板
  25. 'tpl_exec' => THINK_PATH . 'tpl' . DS . 'exec.php',
  26. // 小绿毛
  27. 'app_debug' => THINK_PATH . 'tpl' . DS . 'error_debug.php',
  28. ],
  29. ];

再新建/ThinkMIMI/application/config.php项目公共配置文件,写入以下代码:

  1. <?php
  2. return [
  3. // +----------------------------------------------------------------------
  4. // | 模块设置
  5. // +----------------------------------------------------------------------
  6. // 默认分组名
  7. 'default_module' => 'index',
  8. // 默认控制器名
  9. 'default_controller' => 'Index',
  10. // 默认操作名
  11. 'default_action' => 'index',
  12. // 默认的空控制器名
  13. 'empty_controller' => 'Error',
  14. // +----------------------------------------------------------------------
  15. // | URL设置
  16. // +----------------------------------------------------------------------
  17. // 路由分隔符
  18. 'pathinfo_depr' => '/',
  19. // URL伪静态后缀
  20. 'url_html_suffix' => '.html',
  21. // 是否开启路由表模式
  22. 'url_route_on' => true,
  23. // +----------------------------------------------------------------------
  24. // | 模板设置
  25. // +----------------------------------------------------------------------
  26. 'template' => [
  27. // 模板后缀
  28. 'view_suffix' => '.html',
  29. // 模板引擎标签开始标记
  30. 'tpl_begin' => '{',
  31. // 模板引擎标签结束标记
  32. 'tpl_end' => '}',
  33. ],
  34. // +----------------------------------------------------------------------
  35. // | Session会话设置
  36. // +----------------------------------------------------------------------
  37. 'session' => [
  38. // SESSION 前缀
  39. 'prefix' => 'mimi_',
  40. // SESSION 过期时间(秒)
  41. 'time' => 7200,
  42. ],
  43. // +----------------------------------------------------------------------
  44. // | Cookie会话设置
  45. // +----------------------------------------------------------------------
  46. 'cookie' => [
  47. // SESSION 前缀
  48. 'prefix' => 'mimi_',
  49. // SESSION 过期时间(秒) OR 0永久
  50. 'time' => 0,
  51. // cookie 保存路径
  52. 'path' => '/',
  53. // cookie 有效域名
  54. 'domain' => '',
  55. // cookie 启用安全传输
  56. 'secure' => true,
  57. // httponly设置
  58. 'httponly' => false,
  59. ],
  60. // +----------------------------------------------------------------------
  61. // | 验证码设置
  62. // +----------------------------------------------------------------------
  63. 'verify' => [
  64. // 验证码字体大小(px)
  65. 'fontsize' => 20,
  66. // 验证码图片高度
  67. 'height' => 50,
  68. // 验证码图片宽度
  69. 'width' => 150,
  70. // 验证码位数
  71. 'length' => 4,
  72. // 验证码字体样式
  73. 'ttf' => '6.ttf',
  74. // 验证码过期时间,单位:秒
  75. 'expire' => 60,
  76. // 是否添加混淆曲线
  77. 'curve' => true,
  78. // 是否添加杂点
  79. 'noise' => true,
  80. // 发起验证后是否需要更新验证码
  81. 'update' => true,
  82. ],
  83. // +----------------------------------------------------------------------
  84. // | 数据库设置
  85. // +----------------------------------------------------------------------
  86. 'database' => [
  87. // 数据库类型
  88. 'type' => 'mysql',
  89. // 服务器地址
  90. 'hostname' => '127.0.0.1',
  91. // 数据库名
  92. 'database' => 'api_demo',
  93. // 用户名
  94. 'username' => 'root',
  95. // 密码
  96. 'password' => 'root',
  97. // 端口
  98. 'hostport' => '3306',
  99. // 数据库表前缀
  100. 'prefix' => 'api_',
  101. // 数据库编码默认采用utf8
  102. 'charset' => 'utf8',
  103. ],
  104. ];

最后新建/ThinkMIMI/application/index/config.php默认分组私有配置文件,写入以下代码:

<?php
return [
];

到这一步,朋友们已经可以自由配置读取我们的配置项了,下面我们在index.php入口文件的末尾,写入以下代码来进行测试,
测试完成后记得删除这些代码,以进行接下来的学习。

<?php
# 读取公共配置文件下的二维数组,报错了,因为在这一步的时候,我们并没有合并该配置文件
echo \mimi\Config::get('template.view_suffix');
# 读取框架内部配置文件下的二维数组
echo \mimi\Config::get('template.app_debug');

3、语言包切换设计

语言包切换,在实际开发中经常会用到,例如,中英文网站切换,
语言包一般用于存放一些固定的网站内容,例如用户登录后,我们会给出对应的提示:
您好 XX!之类的,当我们要切换到Hello Word XX!时,
就需要将其固定的内容部分(例如:您好),分别存放在不同的语言包文件中,
然后根据对应的配置进行读取输出。
下面我们来看下新的目录结构:

ThinkMIMI  WEB部署目录(或者子目录)
├─index.php   单一入口文件
├─application 应用目录
│   ├─index 默认项目分组目录
│   │   └─config.php 分组私有配置文件
│   ├─config.php 项目公共配置文件
│   └─autoload_class_map.php 项目可自定义扩展命名空间映射表
└─thinkmimi   框架核心文件目录
    ├─《lang》 语言包存放目录
    │   └─《zh.php》 默认中文语言包
    ├─library 框架核心类文件目录
    │   ├─《Lang.php》 语言包操作类
    │   ├─Config.php 配置文件操作类
    │   └─Loader.php 命名空间类
    ├─config.php 框架内部配置文件
    └─base.php 框架核心核心文件

下面我们先来修改/ThinkMIMI/thinkmimi/base.php文件的代码,在末尾写入以下代码:

# 初始化语言包
\mimi\Lang::run();

下面我们再新建/ThinkMIMI/thinkmimi/libaray/Lang.php文件,写入以下代码:

<?php
namespace mimi;
class Lang {
    /**
     * 语言包
     */
    private static $_LANG = [];
    /**
     * 初始化语言包
    */
    public static function run() {
        $file = THINK_PATH . 'lang' . DS .\mimi\Config::get('default_lang') . EXT;
        if (file_exists($file)) {
            self::$_LANG = require_once $file;
        }
    }
    /**
     * 导入语言包合并,只支持最多二维数组
     * @param array 需要导入的语言包
     * @param string $k 二维数组的键名
    */
    public static function load($data, $k=null) {
        foreach ($data as $key=>$val) {
            if ( is_array($val) )  {
                self::load($val, $key);
            } else {
                if (!empty($k)) {
                    self::$_LANG[$k][$key] = $val;
                } else {
                    self::$_LANG[$key] = $val;
                }
            }
        }
    }
    /**
     * 获取语言包参数
     * @param string $key 语言包键名,为空读取所有
     * @param void
    */
    public static function get($key=null) {
        if ($key===null) { return self::$_LANG; }
        $data    = explode('.', $key);
        $param_A = $data[0];
        # 二维参数读取
        if (!empty($data[1])) {
            $param_B = $data[1];
            if (isset(self::$_LANG[$param_A][$param_B])) {
                return self::$_LANG[$param_A][$param_B];
            }
        }
        # 一维参数读取
        if (isset(self::$_LANG[$param_A])) {
            return self::$_LANG[$param_A];
        }
        return false;
    }
    /**
     * 设置语言包对应参数
     * @param string $key 键名
     * @param string $val 对应的值
     * @param bool
    */
    public static function set($key, $val) {
        $data    = explode('.', $key);
        $param_A = $data[0];
        # 二维参数修改
        if (!empty($data[1])) {
            $param_B = $data[1];
            self::$_LANG[$param_A][$param_B] = $val;
            return true;
        }
        # 一维参数修改
        if (isset(self::$_LANG[$param_A])) {
            self::$_LANG[$param_A] = $val;
            return true;
        }
        return false;
    }
    /**
     * 判断语言包项是否存在 
     * @param string $key 配置键名
     * @param bool
    */
    public static function has($key) {
        $data    = explode('.', $key);
        $param_A = $data[0];
        # 二维参数读取
        if (!empty($data[1])) {
            $param_B = $data[1];
            if (!isset(self::$_LANG[$param_A][$param_B])) {
                return false;
            }
        }
        # 一维参数读取
        if (!isset(self::$_LANG[$param_A])) {
            return false;
        }
        return true;
    }
}

最后我们再新建/ThinkMIMI/thinkmimi/lang/zh.php默认中文语言包文件,写入以下代码:

<?php
# 核心中文语言包
return [
    0 => '您好',
];

下面我们在index.php入口文件的末尾,写入以下代码来进行测试,
测试完成后记得删除这些代码,以进行接下来的学习。

<?php
# 输出语言包
echo \mimi\Lang::get('0');

4框架的错误异常处理机制

如果一个MVC框架,连报错内容都使用PHP最基础的提示方式,那是一件非常丢人的事情。
小黄牛在课件中所使用的错误提示界面,是借用了ThinkPHP5框架的前端界面,所以美观程度还算可以。
下面我们来看下新的目录结构:

下面我们来看下新的目录结构:
ThinkMIMI  WEB部署目录(或者子目录)
├─index.php   单一入口文件
├─application 应用目录
│   ├─index 默认项目分组目录
│   │   └─config.php 分组私有配置文件
│   ├─config.php 项目公共配置文件
│   └─autoload_class_map.php 项目可自定义扩展命名空间映射表
└─thinkmimi   框架核心文件目录
    ├─《tpl》 框架核心模板文件
    │   ├─《error_test.php》 开启DEBUG时的错误提示界面
    │   └─《error_formal.php》 关闭DEBUG时的错误提示界面
    ├─lang 语言包存放目录
    │   └─zh.php 默认中文语言包
    ├─library 框架核心类文件目录
    │   ├─《Error.php》 错误异常重写类
    │   ├─Lang.php 语言包操作类
    │   ├─Config.php 配置文件操作类
    │   └─Loader.php 命名空间类
    ├─config.php 框架内部配置文件
    └─base.php 框架核心核心文件

下面我们先来修改/ThinkMIMI/thinkmimi/base.php文件的代码,在末尾写入以下代码:

# 错误和异常处理机制
\mimi\Error::register();

下面我们再新建/ThinkMIMI/thinkmimi/libaray/Error.php文件,写入以下代码:

<?php
namespace mimi;
class Error {
    /**
     * 注册错误异常监听
     * @return void
    */
    public static function register() {
        # 致命错误捕捉
        register_shutdown_function('\mimi\Error::deadlyError');
        # 异常捕捉
        set_error_handler('\mimi\Error::appError'); 
    }
    /**
     * 普通错误异常捕捉
     * @access public
     * @param int $errno 错误类型
     * @param string $errstr 错误信息
     * @param string $errfile 错误文件
     * @param int $errline 错误行数
     * @param int $errcontext 错误上下文
     * @return void
    */
    public static function appError($errno, $errstr, $errfile, $errline, $errcontext) {
        $error = [];
        switch ($errno) {
            case E_ERROR:
            case E_PARSE:
            case E_CORE_ERROR:
            case E_COMPILE_ERROR:
            case E_USER_ERROR:
                ob_end_clean();
                $error['message'] = $errstr;
                $error['file'] = $errfile;
                $error['line'] = $errline;
                break;
            default:
                $error['message'] = $errstr;
                $error['file'] = $errfile;
                $error['line'] = $errline;
                break;
        }
        self::halt($error);
    }
    /**
     * 致命异常错误捕捉
     * @return void
    */
    public static function deadlyError() {
        if ($e = error_get_last()) {
            $error = [];
            switch($e['type']){
              case E_ERROR:
              case E_PARSE:
              case E_CORE_ERROR:
              case E_COMPILE_ERROR:
              case E_USER_ERROR:  
                ob_end_clean();
                $error['message'] = $e['message'];
                $error['file'] = $e['file'];
                $error['line'] = $e['line'];
                self::halt($error);
                break;
            }
        }
    }
    /**
     * 获取出错文件内容
     * 获取错误的前9行和后9行
     * @param string $file 错文件地址
     * @param int $line 错误行数
     * @return array 错误文件内容
    */
    protected static function getSourceCode($file, $line) {
        $first = ($line - 9 > 0) ? $line - 9 : 1;
        try {
            $contents = file($file);
            $source   = [
                'first'  => $first,
                'source' => array_slice($contents, $first - 1, 19),
            ];
        } catch (Exception $e) {
            $source = [];
        }
        return $source;
    }
    /**
     * 错误输出
     * @param mixed $error 错误
     * @return void
    */
    public static function halt($error) {
        $e = [];
        # 获得错误信息
        $e['file']    = $error['file'];
        $e['line']    = $error['line'];
        $data         = explode('in '.$error['file'], $error['message']);
        $e['message'] = $data[0];
        # 开启调试模式则打印错误信息
        if (APP_DEBUG == true) {
            $e['trace']     = debug_backtrace();
            # 获得错误上下文内容
            $source         = self::getSourceCode($e['file'], $e['line']);
            # 引入详细报错页面
            $exceptionFile = \mimi\Config::get('template.tpl_error_yes');
        } else {
            # 引入简单报错页面
            $exceptionFile = \mimi\Config::get('template.tpl_error_no');
        }
        include $exceptionFile;
        exit;
    }
}

下面我们再新建/ThinkMIMI/thinkmimi/tpl/error_test.php文件,写入以下代码:

<?php
    if(!function_exists('parse_padding')){
        function parse_padding($source)
        {
            $length  = strlen(strval(count($source['source']) + $source['first']));
            return 40 + ($length - 1) * 8;
        }
    }
    if(!function_exists('parse_class')){
        function parse_class($name)
        {
            $names = explode('\\', $name);
            return '<abbr title="'.$name.'">'.end($names).'</abbr>';
        }
    }
    if(!function_exists('parse_file')){
        function parse_file($file, $line)
        {
            return '<a class="toggle" title="'."$file line $line".'">'.basename($file)." line $line".'</a>';
        }
    }
    if(!function_exists('parse_args')){
        function parse_args($args)
        {
            $result = [];
            foreach ($args as $key => $item) {
                switch (true) {
                    case is_object($item):
                        $value = sprintf('<em>object</em>(%s)', parse_class(get_class($item)));
                        break;
                    case is_array($item):
                        if(count($item) > 3){
                            $value = sprintf('[%s, ...]', parse_args(array_slice($item, 0, 3)));
                        } else {
                            $value = sprintf('[%s]', parse_args($item));
                        }
                        break;
                    case is_string($item):
                        if(strlen($item) > 20){
                            $value = sprintf(
                                '\'<a class="toggle" title="%s">%s...</a>\'',
                                htmlentities($item),
                                htmlentities(substr($item, 0, 20))
                            );
                        } else {
                            $value = sprintf("'%s'", htmlentities($item));
                        }
                        break;
                    case is_int($item):
                    case is_float($item):
                        $value = $item;
                        break;
                    case is_null($item):
                        $value = '<em>null</em>';
                        break;
                    case is_bool($item):
                        $value = '<em>' . ($item ? 'true' : 'false') . '</em>';
                        break;
                    case is_resource($item):
                        $value = '<em>resource</em>';
                        break;
                    default:
                        $value = htmlentities(str_replace("\n", '', var_export(strval($item), true)));
                        break;
                }
                $result[] = is_int($key) ? $value : "'$key' => $value";
            }
            return implode(', ', $result);
        }
    }
?>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>System Error</title>
    <meta name="robots" content="noindex,nofollow" />
    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
    <style>
        body{color:#333;font:14px Verdana,"Helvetica Neue",helvetica,Arial,'Microsoft YaHei',sans-serif;margin:0;padding:0 20px 20px;word-break:break-word}h1{margin:10px 0 0;font-size:28px;font-weight:500;line-height:32px}h2{color:#4288ce;font-weight:400;padding:6px 0;margin:6px 0 0;font-size:18px;border-bottom:1px solid #eee}h3.subheading{color:#4288ce;margin:6px 0 0;font-weight:400}h3{margin:12px;font-size:16px;font-weight:bold}abbr{cursor:help;text-decoration:underline;text-decoration-style:dotted}a{color:#868686;cursor:pointer}a:hover{text-decoration:underline}.line-error{background:#f8cbcb}.echo table{width:100%}.echo pre{padding:16px;overflow:auto;font-size:85%;line-height:1.45;background-color:#f7f7f7;border:0;border-radius:3px;font-family:Consolas,"Liberation Mono",Menlo,Courier,monospace}.echo pre>pre{padding:0;margin:0}.col-md-3{width:25%}.col-md-9{width:75%}[class^="col-md-"]{float:left}.clearfix{clear:both}@media only screen and (min-device-width :375px) and (max-device-width :667px){.col-md-3,.col-md-9{width:100%}}.exception{margin-top:20px}.exception .message{padding:12px;border:1px solid #ddd;border-bottom:0 none;line-height:18px;font-size:16px;border-top-left-radius:4px;border-top-right-radius:4px;font-family:Consolas,"Liberation Mono",Courier,Verdana,"微软雅黑"}.exception .code{float:left;text-align:center;color:#fff;margin-right:12px;padding:16px;border-radius:4px;background:#999}.exception .source-code{padding:6px;border:1px solid #ddd;background:#f9f9f9;overflow-x:auto}.exception .source-code pre{margin:0}.exception .source-code pre ol{margin:0;color:#4288ce;display:inline-block;min-width:100%;box-sizing:border-box;font-size:14px;font-family:"Century Gothic",Consolas,"Liberation Mono",Courier,Verdana;padding-left:48px}.exception .source-code pre li{border-left:1px solid #ddd;height:18px;line-height:18px}.exception .source-code pre code{color:#333;height:100%;display:inline-block;border-left:1px solid #fff;font-size:14px;font-family:Consolas,"Liberation Mono",Courier,Verdana,"微软雅黑"}.exception .trace{padding:6px;border:1px solid #ddd;border-top:0 none;line-height:16px;font-size:14px;font-family:Consolas,"Liberation Mono",Courier,Verdana,"微软雅黑"}.exception .trace ol{margin:12px}.exception .trace ol li{padding:2px 4px}.exception div:last-child{border-bottom-left-radius:4px;border-bottom-right-radius:4px}.exception-var table{width:100%;margin:12px 0;box-sizing:border-box;table-layout:fixed;word-wrap:break-word}.exception-var table caption{text-align:left;font-size:16px;font-weight:bold;padding:6px 0}.exception-var table caption small{font-weight:300;display:inline-block;margin-left:10px;color:#ccc}.exception-var table tbody{font-size:13px;font-family:Consolas,"Liberation Mono",Courier,"微软雅黑"}.exception-var table td{padding:0 6px;vertical-align:top;word-break:break-all}.exception-var table td:first-child{width:28%;font-weight:bold;white-space:nowrap}.exception-var table td pre{margin:0}.copyright{margin-top:24px;padding:12px 0;border-top:1px solid #eee}pre.prettyprint .pln{color:#000}pre.prettyprint .str{color:#080}pre.prettyprint .kwd{color:#008}pre.prettyprint .com{color:#800}pre.prettyprint .typ{color:#606}pre.prettyprint .lit{color:#066}pre.prettyprint .pun,pre.prettyprint .opn,pre.prettyprint .clo{color:#660}pre.prettyprint .tag{color:#008}pre.prettyprint .atn{color:#606}pre.prettyprint .atv{color:#080}pre.prettyprint .dec,pre.prettyprint .var{color:#606}pre.prettyprint .fun{color:red}
    </style>
</head>
<body>
    <div class="exception">
        <div class="message">
            <div class="info">
                <div>
                    <h2>ThrowableError in:<?php echo parse_file($e['file'], $e['line']); ?></h2>
                </div>
                <div>
                    <h1>
                        <?php echo '错误内容:'. nl2br(htmlentities($e['message'])); ?><br/>
                        <?php echo '错误地址:'. $e['file']; ?><br/>
                        <?php echo '错误行数:'. $e['line']; ?><br/>
                    </h1>
                </div>
            </div>
        </div>
        <div class="source-code">
            <pre class="prettyprint lang-php"><ol start="<?php echo $source['first']; ?>"><?php foreach ((array) $source['source'] as $key => $value) { ?><li class="line-<?php echo $key + $source['first']; ?>"><code><?php echo htmlentities($value); ?></code></li><?php } ?></ol></pre>
        </div>
        <div class="trace">
            <h2>Call Stack</h2>
            <ol>
                <li><?php echo sprintf('in %s', parse_file($e['file'], $e['line'])); ?></li>
                <?php foreach ((array) $e['trace'] as $value) { ?>
                <li>
                <?php 
                    // Show Function
                    if($value['function']){
                        echo sprintf(
                            'at %s%s%s(%s)', 
                            isset($value['class']) ? parse_class($value['class']) : '',
                            isset($value['type'])  ? $value['type'] : '', 
                            $value['function'], 
                            isset($value['args'])?parse_args($value['args']):''
                        );
                    }
                    // Show line
                    if (isset($value['file']) && isset($value['line'])) {
                        echo sprintf(' in %s', parse_file($value['file'], $value['line']));
                    }
                ?>
                </li>
                <?php } ?>
            </ol>
        </div>
    </div>
    <div class="copyright">
        <a title="官方网站" href="https://xiuxian.junphp.com">ThinkMIMI</a> 
        <span>V<?php echo THINK_VERSION; ?></span> 
        <span>{ 十年磨一贱-为懒人开发设计的低性能框架 }</span>
    </div>
    <script>
        var LINE = <?php echo $e['line']; ?>;
        function $(selector, node){
            var elements;
            node = node || document;
            if(document.querySelectorAll){
                elements = node.querySelectorAll(selector);
            } else {
                switch(selector.substr(0, 1)){
                    case '#':
                        elements = [node.getElementById(selector.substr(1))];
                        break;
                    case '.':
                        if(document.getElementsByClassName){
                            elements = node.getElementsByClassName(selector.substr(1));
                        } else {
                            elements = get_elements_by_class(selector.substr(1), node);
                        }
                        break;
                    default:
                        elements = node.getElementsByTagName();
                }
            }
            return elements;
            function get_elements_by_class(search_class, node, tag) {
                var elements = [], eles, 
                    pattern  = new RegExp('(^|\\s)' + search_class + '(\\s|$)');
                node = node || document;
                tag  = tag  || '*';
                eles = node.getElementsByTagName(tag);
                for(var i = 0; i < eles.length; i++) {
                    if(pattern.test(eles[i].className)) {
                        elements.push(eles[i])
                    }
                }
                return elements;
            }
        }
        $.getScript = function(src, func){
            var script = document.createElement('script');
            script.async  = 'async';
            script.src    = src;
            script.onload = func || function(){};
            $('head')[0].appendChild(script);
        }
        ;(function(){
            var files = $('.toggle');
            var ol    = $('ol', $('.prettyprint')[0]);
            var li    = $('li', ol[0]);   
            // 短路径和长路径变换
            for(var i = 0; i < files.length; i++){
                files[i].ondblclick = function(){
                    var title = this.title;
                    this.title = this.innerHTML;
                    this.innerHTML = title;
                }
            }
            // 设置出错行
            var err_line = $('.line-' + LINE, ol[0])[0];
            err_line.className = err_line.className + ' line-error';
            $.getScript('//cdn.bootcss.com/prettify/r298/prettify.min.js', function(){
                prettyPrint();
                // 解决Firefox浏览器一个很诡异的问题
                // 当代码高亮后,ol的行号莫名其妙的错位
                // 但是只要刷新li里面的html重新渲染就没有问题了
                if(window.navigator.userAgent.indexOf('Firefox') >= 0){
                    ol[0].innerHTML = ol[0].innerHTML;
                }
            });
        })();
    </script>
</body>
</html>

下面我们再新建/ThinkMIMI/thinkmimi/tpl/error_formal.php文件,写入以下代码:

<?php 
if(!function_exists('parse_file')){
    function parse_file($file, $line)
    {
        return '<a class="toggle" title="'."$file line $line".'">'.basename($file)." line $line".'</a>';
    }
}
?>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>System Error</title>
    <meta name="robots" content="noindex,nofollow" />
    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
    <style>
        body{color:#333;font:14px Verdana,"Helvetica Neue",helvetica,Arial,'Microsoft YaHei',sans-serif;margin:0;padding:0 20px 20px;word-break:break-word}h1{margin:10px 0 0;font-size:28px;font-weight:500;line-height:32px}h2{color:#4288ce;font-weight:400;padding:6px 0;margin:6px 0 0;font-size:18px;border-bottom:1px solid #eee}h3.subheading{color:#4288ce;margin:6px 0 0;font-weight:400}h3{margin:12px;font-size:16px;font-weight:bold}abbr{cursor:help;text-decoration:underline;text-decoration-style:dotted}a{color:#868686;cursor:pointer}a:hover{text-decoration:underline}.line-error{background:#f8cbcb}.echo table{width:100%}.echo pre{padding:16px;overflow:auto;font-size:85%;line-height:1.45;background-color:#f7f7f7;border:0;border-radius:3px;font-family:Consolas,"Liberation Mono",Menlo,Courier,monospace}.echo pre>pre{padding:0;margin:0}.col-md-3{width:25%}.col-md-9{width:75%}[class^="col-md-"]{float:left}.clearfix{clear:both}@media only screen and (min-device-width :375px) and (max-device-width :667px){.col-md-3,.col-md-9{width:100%}}.exception{margin-top:20px}.exception .message{padding:12px;border:1px solid #ddd;border-bottom:0 none;line-height:18px;font-size:16px;border-top-left-radius:4px;border-top-right-radius:4px;font-family:Consolas,"Liberation Mono",Courier,Verdana,"微软雅黑"}.exception .code{float:left;text-align:center;color:#fff;margin-right:12px;padding:16px;border-radius:4px;background:#999}.exception .source-code{padding:6px;border:1px solid #ddd;background:#f9f9f9;overflow-x:auto}.exception .source-code pre{margin:0}.exception .source-code pre ol{margin:0;color:#4288ce;display:inline-block;min-width:100%;box-sizing:border-box;font-size:14px;font-family:"Century Gothic",Consolas,"Liberation Mono",Courier,Verdana;padding-left:48px}.exception .source-code pre li{border-left:1px solid #ddd;height:18px;line-height:18px}.exception .source-code pre code{color:#333;height:100%;display:inline-block;border-left:1px solid #fff;font-size:14px;font-family:Consolas,"Liberation Mono",Courier,Verdana,"微软雅黑"}.exception .trace{padding:6px;border:1px solid #ddd;border-top:0 none;line-height:16px;font-size:14px;font-family:Consolas,"Liberation Mono",Courier,Verdana,"微软雅黑"}.exception .trace ol{margin:12px}.exception .trace ol li{padding:2px 4px}.exception div:last-child{border-bottom-left-radius:4px;border-bottom-right-radius:4px}.exception-var table{width:100%;margin:12px 0;box-sizing:border-box;table-layout:fixed;word-wrap:break-word}.exception-var table caption{text-align:left;font-size:16px;font-weight:bold;padding:6px 0}.exception-var table caption small{font-weight:300;display:inline-block;margin-left:10px;color:#ccc}.exception-var table tbody{font-size:13px;font-family:Consolas,"Liberation Mono",Courier,"微软雅黑"}.exception-var table td{padding:0 6px;vertical-align:top;word-break:break-all}.exception-var table td:first-child{width:28%;font-weight:bold;white-space:nowrap}.exception-var table td pre{margin:0}.copyright{margin-top:24px;padding:12px 0;border-top:1px solid #eee}pre.prettyprint .pln{color:#000}pre.prettyprint .str{color:#080}pre.prettyprint .kwd{color:#008}pre.prettyprint .com{color:#800}pre.prettyprint .typ{color:#606}pre.prettyprint .lit{color:#066}pre.prettyprint .pun,pre.prettyprint .opn,pre.prettyprint .clo{color:#660}pre.prettyprint .tag{color:#008}pre.prettyprint .atn{color:#606}pre.prettyprint .atv{color:#080}pre.prettyprint .dec,pre.prettyprint .var{color:#606}pre.prettyprint .fun{color:red}
    </style>
</head>
<body>
<div class="exception">
    <div class="message">
        <div class="info">
            <div>
                <h2>ThrowableError in:<?php echo parse_file($e['file'], $e['line']); ?></h2>
            </div>
            <div>
                <h1>
                    原因:<?php echo nl2br(htmlentities($e['message'])); ?><br/>
                </h1>
            </div>
        </div>
    </div>
 </div>               
<div class="copyright">
    <a title="官方网站" href="https://xiuxian.junphp.com">ThinkMIMI</a> 
    <span>V<?php echo THINK_VERSION;?></span> 
    <span>{ 十年磨一贱-为骚货开发的低性能框架 }</span>
</div>
</body>
</html>

下面我们在index.php入口文件的末尾,写入以下代码来进行测试,
测试完成后记得删除这些代码,以进行接下来的学习。

<?php
# 调用一个不存的函数
qwe();