1、载入框架的命名空间
在一个MVC框架中,命名空间分组管理是一个非常重要的设计,
它可以让我们从日常繁琐的require语句中解脱出来,也不必在担心require会重复引入的问题。
而且命名空间的映射表还可以帮我们优化一些层级非常深的类,不需要编写很长的目录结构,就能use到对应的类。
我们先来看下新的目录结构:
ThinkMIMI WEB部署目录(或者子目录)
├─index.php 单一入口文件
├─application 应用目录
│ └─《autoload_class_map.php》 项目可自定义扩展命名空间映射表
└─thinkmimi 框架核心文件目录
├─《library》 框架核心类文件目录
│ └─《Loader.php》 命名空间类
└─base.php 框架核心核心文件
下面我们先来修改/ThinkMIMI/thinkmimi/base.php文件的代码,
将我们之前测试用的echo THINK_PATH;语句删掉,然后再末尾写入以下代码:
# 载入命名空间
require LIB_PATH . 'Loader.php';
# 注册自动加载
\mimi\Loader::register();
下面我们再新建/ThinkMIMI/thinkmimi/libaray/Loader.php文件,写入以下代码:
<?php
namespace mimi;
class Loader {
/**
* 命名空间-自定义路径映射表
*/
public static $vendorMap = [
'app' => APP_PATH . APP_LICATION,
'mimi' => THINK_PATH . 'library',
];
/**
* 注册自动加载机制
* @return void
*/
public static function register() {
# 注册系统自动加载
spl_autoload_register('\mimi\Loader::autoload', true, true);
# 更新映射表
self::mergeMap();
# 下面可以继续做 Composer 自动加载支持
}
/**
* 自动加载器
* @param string $class 自动传入的命名空间路径
*/
public static function autoload($class) {
# 获得解析后的命名空间绝对路径
$file = self::findFile($class);
if (file_exists($file)) {
# 调用引入
self::includeFile($file);
}
}
/**
* 解析命名空间对应的文件路径
* @param string $class 命名空间路径
* @return string 标准路径
*/
private static function findFile($class) {
$vendor = substr($class, 0, strpos($class, '\\')); // 顶级命名空间
# 取出文件基目录
if (!empty(self::$vendorMap[$vendor])) {
$vendorDir = self::$vendorMap[$vendor];
} else {
$vendorDir = __DIR__ . DS . $vendor;
}
$filePath = substr($class, strlen($vendor)) . EXT; // 文件相对路径
return strtr($vendorDir . $filePath, '\\', DS); // 文件标准路径
}
/**
* 框架映射表 与 扩展映射表合并
*/
private static function mergeMap() {
$file = APP_PATH . APP_LICATION . DS . 'autoload_class_map' . EXT;
if (file_exists($file)) {
$map = require_once $file;
self::$vendorMap = array_merge(self::$vendorMap, $map);
}
}
/**
* 引入文件
*/
private static function includeFile($file) {
require $file;
}
}
最后,我们再新建/ThinkMIMI/application/autoload_class_map.php文件,写入以下代码:
<?php
return [
// 顶级命名空间 => 真实顶级路径
// 'app' => APP_PATH . 'application',
];
到这里我们自定义MVC框架的命名空间与自动加载就完成了,
这时候我们再访问index.php文件,不会有任何输出,但只要不报错即可。
2、配置文件设计与加载
在实际开发中,有可能使用者会经常改动框架的一些配置文件,例如路由使用模式、默认分组目录等。
还有可能会需要自定义一些不属于框架本身的配置项,
同时,我们框架内部,还可能会存在一些不允许被使用者修改的配置项等。
这时候我们设计配置文件管理时,就需要将其分为三层结构:
1、框架内部配置文件
2、项目公共配置文件
3、项目分组独立配置文件
再编写一个Config类,将这3层配置文件合并后,再进行日常的增删改查。
下面我们来看下新的目录结构:
ThinkMIMI WEB部署目录(或者子目录)
├─index.php 单一入口文件
├─application 应用目录
│ ├─《index》 默认项目分组目录
│ │ └─《config.php》 分组私有配置文件
│ ├─《config.php》 项目公共配置文件
│ └─autoload_class_map.php 项目可自定义扩展命名空间映射表
└─thinkmimi 框架核心文件目录
├─library 框架核心类文件目录
│ ├─《Config.php》 配置文件操作类
│ └─Loader.php 命名空间类
├─《config.php》 框架内部配置文件
└─base.php 框架核心核心文件
下面我们先来修改/ThinkMIMI/thinkmimi/base.php文件的代码,在末尾写入以下代码:
# 初始化框架配置项
\mimi\Config::run(THINK_PATH.'config'.EXT);
下面我们再新建/ThinkMIMI/thinkmimi/libaray/Config.php文件,写入以下代码:
<?php
namespace mimi;
class Config {
/**
* 配置项
*/
private static $_CONFIG = [];
/**
* 初始化框架应用配置参数
* @param string $file 框架核心配置路径
*/
public static function run($file) {
if (file_exists($file)) {
self::$_CONFIG = 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::$_CONFIG[$k][$key] = $val;
} else {
self::$_CONFIG[$key] = $val;
}
}
}
}
/**
* 获取配置参数
* @param string $key 配置键名,为空读取所有
* @param void
*/
public static function get($key=null) {
if ($key===null) { return self::$_CONFIG; }
$data = explode('.', $key);
$param_A = $data[0];
# 二维参数读取
if (!empty($data[1])) {
$param_B = $data[1];
if (isset(self::$_CONFIG[$param_A][$param_B])) {
return self::$_CONFIG[$param_A][$param_B];
}
}
# 一维参数读取
if (isset(self::$_CONFIG[$param_A])) {
return self::$_CONFIG[$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::$_CONFIG[$param_A][$param_B] = $val;
return true;
}
# 一维参数修改
if (isset(self::$_CONFIG[$param_A])) {
self::$_CONFIG[$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::$_CONFIG[$param_A][$param_B])) {
return false;
}
}
# 一维参数读取
if (!isset(self::$_CONFIG[$param_A])) {
return false;
}
return true;
}
}
然后我们再新建/ThinkMIMI/thinkmimi/config.php框架内部配置文件,写入以下代码:
<?php
return [
// +------------------------------------------------------------------
// | 应用设置
// +------------------------------------------------------------------
// 默认时区
'default_timezone' => 'PRC',
// 默认全局过滤方法 用逗号分隔多个
'default_filter' => '',
// 默认语言
'default_lang' => 'zh',
// +----------------------------------------------------------------------
// | 模板设置
// +----------------------------------------------------------------------
'template' => [
// DEBUG开启时的错误页面解析模板
'tpl_error_yes' => THINK_PATH . 'tpl' . DS . 'error_test.php',
// DEBUG关闭时的错误页面解析模板
'tpl_error_no' => THINK_PATH . 'tpl' . DS . 'error_formal.php',
// 错误跳转模板
'tpl_error' => THINK_PATH . 'tpl' . DS . 'error.php',
// 警告跳转模板
'tpl_notice' => THINK_PATH . 'tpl' . DS . 'notice.php',
// 正确跳转模板
'tpl_exec' => THINK_PATH . 'tpl' . DS . 'exec.php',
// 小绿毛
'app_debug' => THINK_PATH . 'tpl' . DS . 'error_debug.php',
],
];
再新建/ThinkMIMI/application/config.php项目公共配置文件,写入以下代码:
<?php
return [
// +----------------------------------------------------------------------
// | 模块设置
// +----------------------------------------------------------------------
// 默认分组名
'default_module' => 'index',
// 默认控制器名
'default_controller' => 'Index',
// 默认操作名
'default_action' => 'index',
// 默认的空控制器名
'empty_controller' => 'Error',
// +----------------------------------------------------------------------
// | URL设置
// +----------------------------------------------------------------------
// 路由分隔符
'pathinfo_depr' => '/',
// URL伪静态后缀
'url_html_suffix' => '.html',
// 是否开启路由表模式
'url_route_on' => true,
// +----------------------------------------------------------------------
// | 模板设置
// +----------------------------------------------------------------------
'template' => [
// 模板后缀
'view_suffix' => '.html',
// 模板引擎标签开始标记
'tpl_begin' => '{',
// 模板引擎标签结束标记
'tpl_end' => '}',
],
// +----------------------------------------------------------------------
// | Session会话设置
// +----------------------------------------------------------------------
'session' => [
// SESSION 前缀
'prefix' => 'mimi_',
// SESSION 过期时间(秒)
'time' => 7200,
],
// +----------------------------------------------------------------------
// | Cookie会话设置
// +----------------------------------------------------------------------
'cookie' => [
// SESSION 前缀
'prefix' => 'mimi_',
// SESSION 过期时间(秒) OR 0永久
'time' => 0,
// cookie 保存路径
'path' => '/',
// cookie 有效域名
'domain' => '',
// cookie 启用安全传输
'secure' => true,
// httponly设置
'httponly' => false,
],
// +----------------------------------------------------------------------
// | 验证码设置
// +----------------------------------------------------------------------
'verify' => [
// 验证码字体大小(px)
'fontsize' => 20,
// 验证码图片高度
'height' => 50,
// 验证码图片宽度
'width' => 150,
// 验证码位数
'length' => 4,
// 验证码字体样式
'ttf' => '6.ttf',
// 验证码过期时间,单位:秒
'expire' => 60,
// 是否添加混淆曲线
'curve' => true,
// 是否添加杂点
'noise' => true,
// 发起验证后是否需要更新验证码
'update' => true,
],
// +----------------------------------------------------------------------
// | 数据库设置
// +----------------------------------------------------------------------
'database' => [
// 数据库类型
'type' => 'mysql',
// 服务器地址
'hostname' => '127.0.0.1',
// 数据库名
'database' => 'api_demo',
// 用户名
'username' => 'root',
// 密码
'password' => 'root',
// 端口
'hostport' => '3306',
// 数据库表前缀
'prefix' => 'api_',
// 数据库编码默认采用utf8
'charset' => 'utf8',
],
];
最后新建/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();