runWithRequest () 方法

在 Http 类的 run() 方法中,得到 think\Request 类的实例后,程序接着执行 $response = $this->runWithRequest($request); 。其中,runWithRequest() 方法前面几行如下:
protected function runWithRequest(Request $request)
{
$this->initialize(); // 加载全局中间件
$this->loadMiddleware();
.
.
.
该方法第一行执行 $this->initialize();,对应用进行初始化,接下来详细分析这一初始化操作。
 
Http 类的 initialize() 方法:
protected function initialize()
{
//如果还未初始化,则初始化之
if (!$this->app->initialized()) {
$this->app->initialize();
}
}
实际上是调用 App 类的 initialize() 方法。该方法代码:
public function initialize()
{
// 设置应用状态为已经初始化
$this->initialized = true; //记录开始时间
$this->beginTime = microtime(true);
//记录起始内存使用量
$this->beginMem = memory_get_usage(); // ==( A )== 加载环境变量
// $this->env跟前面的(new App())->http和$this->config都是同样的套路
if (is_file($this->rootPath . '.env')) {
$this->env->load($this->rootPath . '.env');
}
//设置配置文件后缀
$this->configExt = $this->env->get('config_ext', '.php');
// ==( B )== 设置调试模式
$this->debugModeInit(); // ==( C )== 加载应用文件和配置等操作
$this->load(); // 加载框架默认语言包
$langSet = $this->lang->defaultLangSet();
// 框架目录下对应的语言包
// 比如:D:\dev\tp6\vendor\topthink\framework\src\lang\zh-cn.php
$this->lang->load($this->thinkPath . 'lang' . DIRECTORY_SEPARATOR . $langSet . '.php'); // 加载应用默认语言包
// 这个会扫描「app/lang」里面,对应语言包文件夹的所有「.php」文件
// 比如,app/lang/zh-cn/* 下的所有文件
// 然后加载解析
$this->loadLangPack($langSet); // 监听AppInit
$this->event->trigger('AppInit'); // 设置时区
date_default_timezone_set($this->config->get('app.default_timezone', 'Asia/Shanghai')); // ==( D )== 初始化
// 初始化错误和异常处理、注册系统服务和初始化系统服务
foreach ($this->initializers as $initializer) {
$this->make($initializer)->init($this);
} return $this;
}
应用的初始化做了大量的操作,其主要的操作有:加载环境变量、加载配置文件,加载语言包、监听 AppInit、initializers 数组包含的类的初始化。

(A) 加载环境变量

对应语句:$this->env->load($this->rootPath . '.env');,其中,$this->env,与前面的 (new App())->http 原理是一样的(参见第一篇),它可以取得 \think\Env 类的实例。取得 Env 类实例后,调用 load() 方法,传入的参数是.env 文件所在地址。load() 方法实现如下:
public function load(string $file): void
{
$env = parse_ini_file($file, true) ?: [];
$this->set($env);
}
该方法读取.env 文件的值后,调用 set() 方法,将配置保存在 Env 类的 $data 成员变量。set() 方法代码:
public function set($env, $value = null): void
{
if (is_array($env)) {
//全部KEY转为大写字母
$env = array_change_key_case($env, CASE_UPPER); foreach ($env as $key => $val) {
//有二级配置的,转为KEY1_KEY2 => $v 的形式
if (is_array($val)) {
foreach ($val as $k => $v) {
$this->data[$key . '_' . strtoupper($k)] = $v;
}
} else {
$this->data[$key] = $val;
}
}
//ENV的值不是数组的情况
} else {
$name = strtoupper(str_replace('.', '_', $env)); $this->data[$name] = $value;
}
}
从.env 读取到的值大概是这样的:
$this->set($env) 之后得到的大概是这样的:

(B) 调试模式设置

 
$this->debugModeInit() 运行原理详见注释。
protected function debugModeInit(): void
{
// 应用调试模式
if (!$this->appDebug) {
$this->appDebug = $this->env->get('app_debug') ? true : false;
// 关闭错误显示
ini_set('display_errors', 'Off');
}
// 如果不是命令行模式
if (!$this->runningInConsole()) {
// 重新申请一块比较大的buffer
// php缓冲控制
// 参考:https://www.php.net/manual/en/ref.outcontrol.php
// https://www.cnblogs.com/saw2012/archive/2013/01/30/2882451.html
// 新版PHP默认开启缓冲区ob_start(),ob_get_level() == 1
if (ob_get_level() > 0) {
// 相当于ob_get_contents() 和 ob_clean()
// 获取缓冲区内容并删除缓冲区内容
$output = ob_get_clean();
}
// 开启新的缓冲区控制
ob_start();
if (!empty($output)) {
// 由于开启了新的缓冲区控制,
// 这里不会直接输出$output
// 而是等到依次执行了ob_flush()和flash()之后才将内容输出到浏览器
echo $output;
}
}
}
需要注意的是,这里貌似有个 Bug,应该先执行 $this->appDebug = $this->env->get('app_debug') ? true : false; 获取是否是调试模式的配置,然后再判断:if (!$this->appDebug)。

(C) 加载应用文件和配置等操作

接下来执行 $this->load();,「load」方法具体实现如下:
protected function load(): void
{
$appPath = $this->getAppPath(); // 加载「app」目录下的「common.php」文件
if (is_file($appPath . 'common.php')) {
include_once $appPath . 'common.php';
}
// 加载核心目录下的「helper.php」文件
// 可以看到,这里的加载顺序:先「common.php」,后「helper.php」
// 且「helper.php」中的函数包裹在「if (!function_exists('xxx'))」下
// 所以可以在「common.php」文件中覆盖系统定义的助手函数
include_once $this->thinkPath . 'helper.php'; $configPath = $this->getConfigPath(); $files = []; // glob的作用是扫描给定路径模式下的文件,非常好用
// 这里扫描「config」目录下的所有「.php」文件
if (is_dir($configPath)) {
$files = glob($configPath . '*' . $this->configExt);
} foreach ($files as $file) {
// $this->config 还是同样的套路获得了「Config」类的实例
// 「load」的第二个参数为一级配置名,这里传入一个文件名,所有文件名作为一级配置
// 比如「app.php」配置文件,一级配置为「app」
// 在 「Config」类作用域下的操作:
// 「load」加载文件后,通过「parse」方法解析文件内容
// 最后,通过「set」方法将所有配置合并了「config」成员变量
$this->config->load($file, pathinfo($file, PATHINFO_FILENAME));
} // 加载「app」目录下的「event.php」文件
if (is_file($appPath . 'event.php')) {
$this->loadEvent(include $appPath . 'event.php');
}
// 注册自定义的服务
if (is_file($appPath . 'service.php')) {
$services = include $appPath . 'service.php';
foreach ($services as $service) {
$this->register($service);
}
}
}
值得一提的是,程序先加载「common.php」,后加载「helper.php」,而「helper.php」中的函数包裹在「if (!function_exists ('xxx'))」下,所以我们如果有需要,可以在「common.php」文件中覆盖系统定义的助手函数。
除了加载这两个文件,该方法还扫描了「config」目录下的所有配置文件,并将其加载到 Config 类的 $config 成员变量,加载了「app」目录下的「event.php」文件,以及加载并注册自定义的服务。

(D) 初始化错误和异常处理、注册系统服务和初始化系统服务

接着,看初始化函数的最后一段:
foreach ($this->initializers as $initializer) {
$this->make($initializer)->init($this);
}
这几行代码做了比较多的操作:分别实例化包含在里面的类,然后调用其「init」方法。initializers 数组的值如下:
protected $initializers = [
Error::class, //错误处理类
RegisterService::class, //注册系统服务类
BootService::class, //启动系统服务
];
略过系统错误处理类,先看注册系统服务类。值得注意的是,这个类有一个成员变量:
protected $services = [
PaginatorService::class,
ValidateService::class,
ModelService::class,
];
包含了三个系统核心服务。在其 init 方法中,这些服务被注册到系统服务,与前面的自定义服务合并起来,其主要实现代码:
foreach ($services as $service) {
if (class_exists($service)) {
// 注册到系统服务
$app->register($service);
}
}
最后实例化的是启动系统服务类,该类的 init 方法仅调用了 App 类的 boot 方法,该方法的作用是初始化每个系统服务,也就是调用每个服务的 boot 方法。
 
启动系统服务类实现如下:
class BootService
{
public function init(App $app)
{
$app->boot();
}
}
App 类的 boot 方法:
public function boot(): void
{
array_walk($this->services, function ($service) {
$this->bootService($service);
});
}
其中关键是 bootService 方法:
public function bootService($service)
{
if (method_exists($service, 'boot')) {
return $this->invoke([$service, 'boot']);
}
}
该方法分别调用了每个服务的 boot 方法,从而初始化已注册的服务。
从以上代码可以看到,系统注册的服务的来源有三个地方:
  1. 系统自带的,如 PaginatorService,ValidateService,ModelService;
  2. app 目录下,在「service.php」文件中自定义的;
  3. vendor 目录下的「service.php」文件定义的。
初始化之后,「App」类的实例大概是这样子的:
 
更多学习内容可以访问从码农成为架构师的修炼之路

ThinkPHP6 核心分析之应用程序初始化的更多相关文章

  1. ThinkPHP6 核心分析:系统服务

    什么是系统服务?系统服务是对于程序要用到的类在使用前先进行类的标识的绑定,以便容器能够对其进行解析(通过服务类的 register 方法),还有就是初始化一些参数.注册路由等(不限于这些操作,主要是看 ...

  2. 《深入理解Spark:核心思想与源码分析》——SparkContext的初始化(叔篇)——TaskScheduler的启动

    <深入理解Spark:核心思想与源码分析>一书前言的内容请看链接<深入理解SPARK:核心思想与源码分析>一书正式出版上市 <深入理解Spark:核心思想与源码分析> ...

  3. 01-Coredump核心转存&amp;&amp;Linux程序地址分析【转】

    转自:http://www.itwendao.com/article/detail/404132.html 目录(?)[-] 一Core Dump核心转存 二Linux程序地址分析 一Core Dum ...

  4. 【spring源码分析】IOC容器初始化(十一)

    前言:前面分析了doCreateBean中的createBeanInstance函数,接下来分析其剩余流程. 首先贴上doCreateBean函数: // AbstractAutowireCapabl ...

  5. Envoy 源码分析--程序启动过程

    目录 Envoy 源码分析--程序启动过程 初始化 main 入口 MainCommon 初始化 服务 InstanceImpl 初始化 启动 main 启动入口 服务启动流程 LDS 服务启动流程 ...

  6. 【spring源码分析】IOC容器初始化——查漏补缺(五)

    前言:我们知道在Spring中经常使用配置文件的形式对进行属性的赋值,那配置文件的值是怎么赋值到属性上的呢,本文将对其进行分析. 首先了解一个类:PropertySourcesPlaceholderC ...

  7. 在 NetBeans IDE 6.0 中分析 Java 应用程序性能

    NetBeans IDE 6.0 包含一个强大的性能分析工具,可提供与应用程序运行时行为有关的重要信息.通过 NetBeans 性能分析工具,我们可以方便地在 IDE 中监控应用程序的线程状态.CPU ...

  8. 【spring源码分析】IOC容器初始化(总结)

    前言:在经过前面十二篇文章的分析,对bean的加载流程大致梳理清楚了.因为内容过多,因此需要进行一个小总结. 经过前面十二篇文章的漫长分析,终于将xml配置文件中的bean,转换成我们实际所需要的真正 ...

  9. 【spring源码分析】IOC容器初始化(二)

    前言:在[spring源码分析]IOC容器初始化(一)文末中已经提出loadBeanDefinitions(DefaultListableBeanFactory)的重要性,本文将以此为切入点继续分析. ...

  10. 【spring源码分析】IOC容器初始化(三)

    前言:在[spring源码分析]IOC容器初始化(二)中已经得到了XML配置文件的Document实例,下面分析bean的注册过程. XmlBeanDefinitionReader#registerB ...

随机推荐

  1. Raspberry Pi 3 Basic Command and Information

    default username : pi default password : raspberry enter system setting interface : sudo raspi-confi ...

  2. [转]IP动态切换脚本

    因为公司办公室要设置固定IP才行,而家里的IP段和公司是不一样的,家里采用了DHCP机制,这样每次就得改IP设置,很是不方便,就写了这个脚本来动态切换,很流畅的说!WINXP,WIN7测试通过~嘿嘿~ ...

  3. 微信支付-JSAPI支付V3-查询退款

    接口地址 接口链接:https://api.mch.weixin.qq.com/pay/refundquery 是否需要证书 不需要. 请求参数 字段名 变量名 必填 类型 示例值 描述 公众账号ID ...

  4. python学习第五天 List和tuple类型介绍及其List切片

    List 和tuple: python提供一种类似C语言数组的类型,但是使用起来确是相当的简洁.那就讲讲这神奇的python中list 和tuple吧. List类型: 1.直接贴代码: L = [' ...

  5. JS——操作内容、操作相关元素

    操作内容:普通元素.innerHTML = "值": 会把标记执行渲染普通元素.innerText = "值": 将值原封不动的展示出来,即使里面有标记 var ...

  6. Android Studio环境下搭建ReactNative

    1.安装Android Studio首先肯定是 安装Android Studio(包含SDK)(国内推荐)ps:这里有一点要注意,需要为SDK配置环境变量,名称必须为ANDROID_HOME 2.安装 ...

  7. [Swift]LeetCode153. 寻找旋转排序数组中的最小值 | Find Minimum in Rotated Sorted Array

    Suppose an array sorted in ascending order is rotated at some pivot unknown to you beforehand. (i.e. ...

  8. findbugs插件使用

    本文以idea的插件举例子 介绍 Findbugs是一个静态分析工具,它检查类或者JAR 文件,将字节码与一组缺陷模式进行对比以发现可能的问题. idea安装 自此,插件安装完毕,需要重启idea才生 ...

  9. ELK--filebeat详解

    Filebeat提供了几种不同的方式来启用模块: 在modules.d编辑目录中启用模块配置 运行Filebeat 编辑时启用模块 在filebeat.yml文件编辑中启用模块配置 例如,要在 目录中 ...

  10. 笨办法11提问-raw_input

    源代码如下,有个改动 print "How old are you?", age = raw_input() print "How tall are you?" ...