runWithRequest () 方法
在 Http
类的 run()
方法中,得到 think\\Request
类的实例后,程序接着执行 $response = $this->runWithRequest(request);
。其中,runWithRequest()
方法前面几行如下:
1 2 3 4 5 6 7 8 9 | protected function runWithRequest(Request $request )
{
$this ->initialize();
$this ->loadMiddleware();
.
.
.
|
Salin selepas log masuk
该方法第一行执行 $this->initialize();
,对应用进行初始化,接下来详细分析这一初始化操作。
Http
类的 initialize()
方法:
1 2 3 4 5 6 7 | protected function initialize()
{
if (! $this ->app->initialized()) {
$this ->app->initialize();
}
}
|
Salin selepas log masuk
实际上是调用 App
类的 initialize()
方法。该方法代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | public function initialize()
{
$this ->initialized = true;
$this ->beginTime = microtime(true);
$this ->beginMem = memory_get_usage();
if ( is_file ( $this ->rootPath . '.env' )) {
$this ->env->load( $this ->rootPath . '.env' );
}
$this ->configExt = $this ->env->get( 'config_ext' , '.php' );
$this ->debugModeInit();
$this ->load();
$langSet = $this ->lang->defaultLangSet();
$this ->lang->load( $this ->thinkPath . 'lang' . DIRECTORY_SEPARATOR . $langSet . '.php' );
$this ->loadLangPack( $langSet );
$this ->event->trigger( 'AppInit' );
date_default_timezone_set( $this ->config->get( 'app.default_timezone' , 'Asia/Shanghai' ));
foreach ( $this ->initializers as $initializer ) {
$this ->make( $initializer )->init( $this );
}
return $this ;
}
|
Salin selepas log masuk
应用的初始化做了大量的操作,其主要的操作有:加载环境变量、加载配置文件,加载语言包、监听 AppInit、initializers 数组包含的类的初始化。
(A) 加载环境变量
对应语句:$this->env->load($this->rootPath . ‘.env’);
,其中,$this->env
,与前面的 (new App())->http
原理是一样的(参见第一篇),它可以取得 \think\Env
类的实例。取得 Env
类实例后,调用 load()
方法,传入的参数是.env
文件所在地址。load()
方法实现如下:
1 2 3 4 5 | public function load(string $file ): void
{
$env = parse_ini_file ( $file , true) ?: [];
$this ->set( $env );
}
|
Salin selepas log masuk
该方法读取.env
文件的值后,调用 set()
方法,将配置保存在 Env
类的 $data
成员变量。set()
方法代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | public function set( $env , $value = null): void
{
if ( is_array ( $env )) {
$env = array_change_key_case ( $env , CASE_UPPER);
foreach ( $env as $key => $val ) {
if ( is_array ( $val )) {
foreach ( $val as $k => $v ) {
$this ->data[ $key . '_' . strtoupper ( $k )] = $v ;
}
} else {
$this ->data[ $key ] = $val ;
}
}
} else {
$name = strtoupper ( str_replace ( '.' , '_' , $env ));
$this ->data[ $name ] = $value ;
}
}
|
Salin selepas log masuk
从.env
读取到的值大概是这样的:

$this->set($env)
之后得到的大概是这样的:

(B) 调试模式设置
$this->debugModeInit()
运行原理详见注释。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | protected function debugModeInit(): void
{
if (! $this ->appDebug) {
$this ->appDebug = $this ->env->get( 'app_debug' ) ? true : false;
ini_set ( 'display_errors' , 'Off' );
}
if (! $this ->runningInConsole()) {
if (ob_get_level() > 0) {
$output = ob_get_clean();
}
ob_start();
if (! empty ( $output )) {
echo $output ;
}
}
}
|
Salin selepas log masuk
需要注意的是,这里貌似有个 Bug,应该先执行 $this->appDebug = $this->env->get('app\_debug') ? true : false;
获取是否是调试模式的配置,然后再判断:if(!$this->appDebug)
。
(C)加载应用文件和配置等操作
接下来执行 $this->load();
,「load」方法具体实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | protected function load(): void
{
$appPath = $this ->getAppPath();
if ( is_file ( $appPath . 'common.php' )) {
include_once $appPath . 'common.php' ;
}
include_once $this ->thinkPath . 'helper.php' ;
$configPath = $this ->getConfigPath();
$files = [];
if ( is_dir ( $configPath )) {
$files = glob ( $configPath . '*' . $this ->configExt);
}
foreach ( $files as $file ) {
$this ->config->load( $file , pathinfo ( $file , PATHINFO_FILENAME));
}
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 );
}
}
}
|
Salin selepas log masuk
值得一提的是,程序先加载「common.php」,后加载「helper.php」,而「helper.php」中的函数包裹在「if (!function_exists (‘xxx’))」下,所以我们如果有需要,可以在「common.php」文件中覆盖系统定义的助手函数。
除了加载这两个文件,该方法还扫描了「config」目录下的所有配置文件,并将其加载到 Config
类的 $config
成员变量,加载了「app」目录下的「event.php」文件,以及加载并注册自定义的服务。
(D) 初始化错误和异常处理、注册系统服务和初始化系统服务
接着,看初始化函数的最后一段:
1 2 3 | foreach ( $this ->initializers as $initializer ) {
$this ->make( $initializer )->init( $this );
}
|
Salin selepas log masuk
这几行代码做了比较多的操作:分别实例化包含在里面的类,然后调用其「init」方法。initializers
数组的值如下:
1 2 3 4 5 | protected $initializers = [
Error:: class ,
RegisterService:: class ,
BootService:: class ,
];
|
Salin selepas log masuk
略过系统错误处理类,先看注册系统服务类。值得注意的是,这个类有一个成员变量:
1 2 3 4 5 | protected $services = [
PaginatorService:: class ,
ValidateService:: class ,
ModelService:: class ,
];
|
Salin selepas log masuk
包含了三个系统核心服务。在其 init
方法中,这些服务被注册到系统服务,与前面的自定义服务合并起来,其主要实现代码:
1 2 3 4 5 6 | foreach ( $services as $service ) {
if ( class_exists ( $service )) {
$app ->register( $service );
}
}
|
Salin selepas log masuk
最后实例化的是启动系统服务类,该类的 init
方法仅调用了 App
类的 boot
方法,该方法的作用是初始化每个系统服务,也就是调用每个服务的 boot
方法。
启动系统服务类实现如下:
1 2 3 4 5 6 7 | class BootService
{
public function init(App $app )
{
$app ->boot();
}
}
|
Salin selepas log masuk
App
类的 boot
方法:
1 2 3 4 5 6 | public function boot(): void
{
array_walk ( $this ->services, function ( $service ) {
$this ->bootService( $service );
});
}
|
Salin selepas log masuk
其中关键是 bootService
方法:
1 2 3 4 5 6 | public function bootService( $service )
{
if (method_exists( $service , 'boot' )) {
return $this ->invoke([ $service , 'boot' ]);
}
}
|
Salin selepas log masuk
该方法分别调用了每个服务的 boot
方法,从而初始化已注册的服务。
从以上代码可以看到,系统注册的服务的来源有三个地方:
- 系统自带的,如
PaginatorService
,ValidateService
,ModelService
;
- app 目录下,在「service.php」文件中自定义的;
- vendor 目录下的「service.php」文件定义的。
初始化之后,「App」类的实例大概是这样子的:

Atas ialah kandungan terperinci 解析ThinkPHP6应用程序初始化. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!