功能有以下三点
对应四行代码
require CORE_PATH . 'Loader.php';
\think\Loader::register();
\think\Error::register();
\think\Config::set(include THINK_PATH . 'convention' . EXT);
常量定义就在这里不重复了。
先来说说Loader.php,从文件名称上也能大概猜到是与加载功能相关,其类内部存在与composer相似的定义变量,这里不列举,为了存储命名空间的映射等信息。
列举下内部调用方法,逐个说明稍微有点多。
Loader::register()方法
//注册系统自动加载 加载方法为类内部的autoload方法,同时设置若无法注册则抛出异常,并添加函数到队列首部
spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true);
//判断vendor内部是否存在composer目录,一般都是存在的,如果没有,我们也最好养成使用composer的习惯。
if (PHP_VERSION_ID >= 50600 && is_file(VENDOR_PATH . 'composer' . DS . 'autoload_static.php')) {
//判断PHP的版本号和autoload_static.php文件存在
require VENDOR_PATH . 'composer' . DS . 'autoload_static.php';
//因为composer/autoload_static.php内包含类的类名是无规则的,所以通过查找declared类并通过array_pop出栈操作获取
$declaredClass = get_declared_classes();
$composerClass = array_pop($declaredClass);
//property_exists — 检查对象或类是否具有该属性
foreach (['prefixLengthsPsr4', 'prefixDirsPsr4', 'fallbackDirsPsr4', 'prefixesPsr0', 'fallbackDirsPsr0', 'classMap', 'files'] as $attr) {
//若如上几个静态属性存在,则复制到当前类,用于后续的自动加载
if (property_exists($composerClass, $attr)) {
self::${$attr} = $composerClass::${$attr};
}
}
}
autoload_static.php已包含指定文件composer实现的自动加载规则,将其引入,并将映射关系转存储到当前类内部变量内。
等于是将composer的加载规则拿来,自己替代composer的功能。
若PHP版本号不满足或auto_static.php文件不存在会怎么办?
调用类内部的registerComposerLoader()静态方法,逐个判断autoload_namespaces.php、autoload_psr4.php、autoload_classmap.php、autoload_files.php文件是否存在,引入赋值。
继续往下
//添加thinkphp框架自身需要的命名空间映射信息
self::addNamespace([
'think' => LIB_PATH . 'think' . DS,
'behavior' => LIB_PATH . 'behavior' . DS,
'traits' => LIB_PATH . 'traits' . DS,
]);
/*
实际对应关系
think==>thinkphp/library/think/
behavior==>thinkphp/library/behavior/
traits==>thinkphp/library/traits/
*/
//查找/runtime/classmap.php,引入已缓存的类库映射文件。
if (is_file(RUNTIME_PATH . 'classmap' . EXT)) {
self::addClassMap(__include_file(RUNTIME_PATH . 'classmap' . EXT));
}
//加载autofile文件
self::loadComposerAutoloadFiles();
//设置自动加载 extend 目录
self::$fallbackDirsPsr4[] = rtrim(EXTEND_PATH, DS);
所以再来看调用\think\Loader::register();后有什么作用。调用系统函数spl_autoload_register()实现自动加载,引入composer加载规则,定义thinkphp框架的核心库命名空间映射,加载classmap、autofile。autoload()方法不再详细阐述
将调用方法\think\Error::register()。值得一提的是,Error类未进行require或include处理,而是通过刚刚已实现的autoload来自动载入
Error::register()方法
/**
* 注册异常处理
* @access public
* @return void
*/
public static function register()
{
//设置报告所有错误
error_reporting(E_ALL);
//设置用户自定义的错误处理程序
set_error_handler([__CLASS__, 'appError']);
//设置用户定义的异常处理函数
set_exception_handler([__CLASS__, 'appException']);
//设置脚本中止时执行的函数
register_shutdown_function([__CLASS__, 'appShutdown']);
}
error_reporting(E_ALL);设置错误级别,这种情况哪怕是一个变量未定义被使用也会直接报错。
set_error_handler([__CLASS__, ‘appError’]);设置类内部的appError为自定义错误捕捉方法。
set_exception_handler([__CLASS__, ‘appException’]);设置类内部的appException为自定义异常捕捉方法。
register_shutdown_function([__CLASS__, ‘appShutdown’]);设置类内部的appShutdown为结束调用方法。
错误触发处理方法
public static function appError($errno, $errstr, $errfile = '', $errline = 0)
{
$exception = new ErrorException($errno, $errstr, $errfile, $errline);
if (error_reporting() & $errno) {
throw $exception;
}
self::getExceptionHandler()->report($exception);
}
第一句:$exception = new ErrorException($errno, $errstr, $errfile, $errline);
将错误进行封装成异常类,ErrorException是tp自行编写的类,其最终基类为PHP库的Exception。作用就是为了将一系列错误信息放进对象里,方便后续获取。
if (error_reporting() & $errno) {
throw $exception;
}
前文已通过error_reporting(E_ALL)设置错误级别,此处判断若当前触发的错误为预设置的一种,那么抛出这个自定义异常,交由后续的appException处理。
self::getExceptionHandler()->report($exception);
既然错误已转成异常且经常判断抛出交由另外方法处置,那么此处又是什么作用呢?我的理解是,当前默认的错误级别为E_ALL,理论上应该已包含所有错误。但是如果另行设置了级别,就不会触发进这个if里,相当于appError捕捉到了错误,但是没做任何处理就结束了。
这句代码作用是将错误信息写入到日志中,让错误有迹可循。
getExceptionHandler()方法实例化一个Handle类,默认为think\exception\Handle,我们也可自定义Handle类,但是必须继承该对象。
Handle的report方法,接收Exception类型的参数,区分debug和部署模式将错误信息输出到runtime的日志内。
异常触发处理方法
public static function appException($e)
{
//异常类型判断
if (!$e instanceof \Exception) {
$e = new ThrowableError($e);
}
//打印日志
$handler = self::getExceptionHandler();
$handler->report($e);
if (IS_CLI) { //CLI方式访问
$handler->renderForConsole(new ConsoleOutput, $e);
} else {
$handler->render($e)->send();
}
}
先看一下第一个if判断,若$e非Exception类,则新实例化ThrowableError,涉及到Throwable,从PHP7版本引入,是Exception和Error的基类,貌似是来了一波面向接口编程。这里不谈ThrowableError,比较奇怪的是为什么appException触发携带的$e变量会有可能非Exception类型呢?
接着打印日志。若是CLI访问,则使用think\console\Output,否则 h a n d l e r − > r e n d e r ( handler->render( handler−>render(e)返回包装了Exception内容的Response实例,并调用send()方法将错误信息发送到客户端。在开发途中,往往基于浏览器的调用来观察执行结果,若出现错误,提示信息就基于此来展示给我们。
脚本结束执行函数
public static function appShutdown()
{
//未处理异常或错误捕捉
if (!is_null($error = error_get_last()) && self::isFatal($error['type'])) {
self::appException(new ErrorException(
$error['type'], $error['message'], $error['file'], $error['line']
));
}
//输出日志
Log::save();
}
继续来看看appShutdown()。第一要强调说明,PHP脚本结束后一定会调用该方法,我理解就一个扫尾函数吧。
先调用error_get_last()判断是否有未经捕捉的错误或异常信息,实测如果已经触发到appError和appException,该函数将返回null。若未被捕捉,判断到错误信息,判断错误级别,若达到默认定义的标准,则封装异常信息调用appException,所以在TP5中,甭管出现什么错误,最终的调用处理显示全归功与appException。最后Log::save()将日志输出。
框架惯例配置文件
convention.php被称为惯例配置文件,可以理解为,一个框架运行,它必须要经过一系列的配置,并在运行中基于配置的内容来实现不一样的效果,这是实现了自由的编程。比如日常开发时数据库、域名地址、接口地址等参数放置到单独文件内,那么代码文件在改动后,可随意覆盖,不用担心本地和测试环境参数的问题。
TP5有以下配置类型:
惯例配置->应用配置->扩展配置->场景配置->模块配置->动态配置
从左到右顺序为从下到上。上级优先级高于下级,其实就是下级先将信息存储到数据,若存在上级,则将数组内数据覆盖,以此类推。那么惯例配置将所有默认的选项都已配置完成,这是项目运行的前提,在后续开发者有自定义要求时,可在自行添加,建议加在更上级。