1、启动顺序
执行: Container::get('app')->run()->send();
文件位置:项目名/public/index.php
解释:
根据上文调用关系为index.php->base.php->Error::register()->register_shutdown_function()。
register_shutdown_function()为注册一个会在php中止时执行的函数,代码为 register_shutdown_function([__CLASS__, 'appShutdown'])。
appShutdown()内容:
public static function appShutdown()
{
if (!is_null($error = error_get_last()) && self::isFatal($error['type'])) {
// 将错误信息托管至think\ErrorException
$exception = new ErrorException($error['type'], $error['message'], $error['file'], $error['line']);
self::appException($exception);
}
// 写入日志
Container::get('log')->save();
}
error_get_last()获取最后一次的错误信息,程序结束自动调用。
这里是有错误信息则用自定义的异常类处理之后log保存。
总体意思就为,在php程序执行结束时日志保存最后一次报错。
若是在base.php中Error::register()后执行exit(),则此时没有报错,文件加载(autoload()打印)顺序为think\Error 、think\Container 、think\Log 、think\LoggerInterface。
这个LoggerInterface没有对应文件,但是被log继承,所以会走下think\Loader::autoload()方法,但是think\Loader::findFile()会返回false,有对应文件的返回文件位置再加载。
根据index.php,base.php执行完执行Container::get('app')->run()->send()正式开始启动app类,此时Error(错误处理)、Container(容器)、Log(日志),都是加载好的。
Container类get()调用顺序get()->make()。make函数根据get函数的参数判断$bind数组中是否有值,有值则取对应值再调用一次make,没有则调用invokeClass()用反代理创建类实例。
所以Container::get('log')执行两次,Container::get('think/Log')执行一次,Container::get('app')同理,最后返回对应类的实例。
之后base.php实现LoggerInterface类,并设置类库别名,其中大多数类为think\Facade子类。
think\Facade为门面设计模式。
think\Container为容器设计模式。
从匿名函数(闭包特性)到 PHP 设计模式之容器模式 - Sefa - 博客园
Container调用invokeClass()里会调用其他类的__make()函数。加载log类的过程中,其__make()的参数为think\App和think\Config,这俩类的构造会被调用。
#thinkphp/log
public static function __make(App $app, Config $config)
{
return (new static($app))->init($config->pull('log'));
}
public function init($config = [])
{
$type = isset($config['type']) ? $config['type'] : 'File';
$this->config = $config;
unset($config['type']);
if (!empty($config['close'])) {
$this->allowWrite = false;
}
$this->driver = Loader::factory($type, '\\think\\log\\driver\\', $config);
return $this;
}
#thinkphp/Config
public function __construct($path = '', $ext = '.php')
{
$this->path = $path;
$this->ext = $ext;
$this->yaconf = class_exists('Yaconf');
}
#thinkphp/App
public function __construct($appPath = '')
{
$this->thinkPath = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR;
$this->path($appPath);
}
Config构造中使用一个Yaconf类,Yaconf为php扩展,用于管理配置文件,可以与项目文件分离,可用于管理不用项目,比如前后端分离的两个项目可以使用相同配置文件。
think\Log用init()函数初始化的时候获取$config->pull('log')为参数,作为设置日志引擎用的变量,但是此时为空,因为在没按Yaconf的情况下,并且仅初始化并没用设置$this->config。tp5.1中config文件夹下有多个文件,不同用处的配置都在不同文件中设置。根据代码,日志的默认引擎就是file,所以最后加载think\Log\dirver\File。
#think/Log
public function init($config = [])
{
$type = isset($config['type']) ? $config['type'] : 'File';
$this->config = $config;
unset($config['type']);
if (!empty($config['close'])) {
$this->allowWrite = false;
}
$this->driver = Loader::factory($type, '\\think\\log\\driver\\', $config);
return $this;
}
#think/Cofig
public function pull($name)
{
$name = strtolower($name);
if ($this->yaconf) {
$yaconfName = $this->getYaconfName($name);
if (Yaconf::has($yaconfName)) {
$config = Yaconf::get($yaconfName);
return isset($this->config[$name]) ? array_merge($this->config[$name], $config) : $config;
}
}
return isset($this->config[$name]) ? $this->config[$name] : [];
}
总结上文,Container::get('log')->save()是通过error_get_last()调用所以think\Log、think\Congid、Yaconf、think\Log\driver\File会在每次程序执行完后调用,base.php执行完之后调用think\App::run()->send()。
2、think\App
该类设置版本号,定义为常量const VERSION。继承think\Container类,think\Container类中重新定义魔术方法,其中__get()调用think\Container::make()方法,即think\App中调用属性会获取 think\Container::bind或think\Container::name中对应的类的实例对象。
#think\App
public function run()
{
try {
// 初始化应用
$this->initialize();
// 监听app_init
$this->hook->listen('app_init');
if ($this->bindModule) {
// 模块/控制器绑定
$this->route->bind($this->bindModule);
} elseif ($this->config('app.auto_bind_module')) {
// 入口自动绑定
$name = pathinfo($this->request->baseFile(), PATHINFO_FILENAME);
if ($name && 'index' != $name && is_dir($this->appPath . $name)) {
$this->route->bind($name);
}
}
// 监听app_dispatch
$this->hook->listen('app_dispatch');
$dispatch = $this->dispatch;
if (empty($dispatch)) {
// 路由检测
$dispatch = $this->routeCheck()->init();
}
// 记录当前调度信息
$this->request->dispatch($dispatch);
// 记录路由和请求信息
if ($this->appDebug) {
$this->log('[ ROUTE ] ' . var_export($this->request->routeInfo(), true));
$this->log('[ HEADER ] ' . var_export($this->request->header(), true));
$this->log('[ PARAM ] ' . var_export($this->request->param(), true));
}
// 监听app_begin
$this->hook->listen('app_begin');
// 请求缓存检查
$this->checkRequestCache(
$this->config('request_cache'),
$this->config('request_cache_expire'),
$this->config('request_cache_except')
);
$data = null;
} catch (HttpResponseException $exception) {
$dispatch = null;
$data = $exception->getResponse();
}
$this->middleware->add(function (Request $request, $next) use ($dispatch, $data) {
return is_null($data) ? $dispatch->run() : $data;
});
$response = $this->middleware->dispatch($this->request);
// 监听app_end
$this->hook->listen('app_end', $response);
return $response;
}
public function initialize()
{
if ($this->initialized) {
return;
}
$this->initialized = true;
$this->beginTime = microtime(true);
$this->beginMem = memory_get_usage();
$this->rootPath = dirname($this->appPath) . DIRECTORY_SEPARATOR;
$this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR;
$this->routePath = $this->rootPath . 'route' . DIRECTORY_SEPARATOR;
$this->configPath = $this->rootPath . 'config' . DIRECTORY_SEPARATOR;
static::setInstance($this);
$this->instance('app', $this);
// 加载环境变量配置文件
if (is_file($this->rootPath . '.env')) {
$this->env->load($this->rootPath . '.env');
}
$this->configExt = $this->env->get('config_ext', '.php');
// 加载惯例配置文件
$this->config->set(include $this->thinkPath . 'convention.php');
// 设置路径环境变量
$this->env->set([
'think_path' => $this->thinkPath,
'root_path' => $this->rootPath,
'app_path' => $this->appPath,
'config_path' => $this->configPath,
'route_path' => $this->routePath,
'runtime_path' => $this->runtimePath,
'extend_path' => $this->rootPath . 'extend' . DIRECTORY_SEPARATOR,
'vendor_path' => $this->rootPath . 'vendor' . DIRECTORY_SEPARATOR,
]);
$this->namespace = $this->env->get('app_namespace', $this->namespace);
$this->env->set('app_namespace', $this->namespace);
// 注册应用命名空间
Loader::addNamespace($this->namespace, $this->appPath);
// 初始化应用
$this->init();
// 开启类名后缀
$this->suffix = $this->config('app.class_suffix');
// 应用调试模式
$this->appDebug = $this->env->get('app_debug', $this->config('app.app_debug'));
$this->env->set('app_debug', $this->appDebug);
if (!$this->appDebug) {
ini_set('display_errors', 'Off');
} elseif (PHP_SAPI != 'cli') {
//重新申请一块比较大的buffer
if (ob_get_level() > 0) {
$output = ob_get_clean();
}
ob_start();
if (!empty($output)) {
echo $output;
}
}
// 注册异常处理类
if ($this->config('app.exception_handle')) {
Error::setExceptionHandler($this->config('app.exception_handle'));
}
// 注册根命名空间
if (!empty($this->config('app.root_namespace'))) {
Loader::addNamespace($this->config('app.root_namespace'));
}
// 加载composer autofile文件
Loader::loadComposerAutoloadFiles();
// 注册类库别名
Loader::addClassAlias($this->config->pull('alias'));
// 数据库配置初始化
Db::init($this->config->pull('database'));
// 设置系统时区
date_default_timezone_set($this->config('app.default_timezone'));
// 读取语言包
$this->loadLangPack();
// 路由初始化
$this->routeInit();
}
think\App::initialize()先加载thinkphp/convention.php文件。
之后调用$this->init()函数,加载application的init.php、tags.php、common.php、helper.php、middleware.php、provider.php、config文件夹。
init.php文件在application文件夹下不存在会加载runtime文件下的该同名文件。
config文件夹在application文件夹下没有的情况会加载think\App::configPath值对应路径下的config文件夹,即根目录下的config文件夹。
加载config文件夹下的文件时会判断文件后缀为php还是yaml,yaml需要yaml_parse_file()可用,即yaml扩展已安装并可用。
加载其余配置文件后,会把对应的文件名(没有后缀名)作为think\Config的属性名,文件内容作为属性值。
3、think\facade\App
其中的注释内容如@see、@metho等为phpdocumentor内容,用于文档生成。
phpdocumento官网:phpDocumentor
phpdoc tag 解释:phpDocumentor
该类继承think\Facade。