ThinkPHP5源码学习篇--base.php

base.php运行过程解析

功能有以下三点

  1. 定义系统运行常量
  2. 引入Loader类
  3. 注册自动加载类、注册异常处理类、引入管理配置信息

对应四行代码

require CORE_PATH . 'Loader.php';
\think\Loader::register();
\think\Error::register();
\think\Config::set(include THINK_PATH . 'convention' . EXT);

Loader.php

常量定义就在这里不重复了。
先来说说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()方法不再详细阐述


Error.php

将调用方法\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

框架惯例配置文件

convention.php被称为惯例配置文件,可以理解为,一个框架运行,它必须要经过一系列的配置,并在运行中基于配置的内容来实现不一样的效果,这是实现了自由的编程。比如日常开发时数据库、域名地址、接口地址等参数放置到单独文件内,那么代码文件在改动后,可随意覆盖,不用担心本地和测试环境参数的问题。

TP5有以下配置类型:
惯例配置->应用配置->扩展配置->场景配置->模块配置->动态配置

从左到右顺序为从下到上。上级优先级高于下级,其实就是下级先将信息存储到数据,若存在上级,则将数组内数据覆盖,以此类推。那么惯例配置将所有默认的选项都已配置完成,这是项目运行的前提,在后续开发者有自定义要求时,可在自行添加,建议加在更上级。

你可能感兴趣的:(PHP)