前几天官方发布了新版本 2.0.13,虽然修复了若干bug,但毕竟是发布的小版本,所以我们后期对源码的分析依然以版本 2.0.12 为准。
对 ServiceLocator 和 Component 的讲解之前,我们对应用 Yii::$app 和其复杂的父类关系做了一个说明,并简述了应用生命周期的预初始化工作,今天我们准备接着谈谈应用的初始化工作。
我们再强调一下,目前我们所分析的yii2源码以2.0.12为准,万一有同学不经意间升级了版本,可能看到的 yii\base\Object 就是空的类了(php 7.2开始 类名 “Object”无效,2.0.13版本废弃了 yii\base\Object,以 yii\base\BaseObject 为准了)。
说正文,本篇文章最好紧接着文章【应用的生命周期之预初始化】阅读,时间较长,有需要可以再快速阅读一遍。
我们从 yii\base\Object::__construct 构造方法中调用 $this->init() 我们继续分析。
从应用的角度来说,$this 指的就是 yii\web\Application 类的实例 Yii::$app,这里调用的init方法自然就是 yii\web\Application的init方法,我们在其父类 yii\base\Application 中找到对应的init方法,yii\base\Application::init方法如下1
2
3
4
5public function init()
{
$this->state = self::STATE_INIT;
$this->bootstrap();
}
我们看到init方法在标记了当前请求的生命周期状态为 yii\base\Application::STATE_INIT之后,紧接着去调用了 yii\web\Application::bootstrap方法,这个方法做了什么,我们一起看一下1
2
3
4
5
6
7
8protected function bootstrap()
{
$request = $this->getRequest();
Yii::setAlias('@webroot', dirname($request->getScriptFile()));
Yii::setAlias('@web', $request->getBaseUrl());
parent::bootstrap();
}
yii\web\Application::bootstrap 方法的实现比较简单
调用 yii\web\Application::getRequest 方法,即通过我们前面介绍的 ServiceLocator 服务定位容器获取request组件(前面说过,核心组件在预初始化阶段都被注册了,所以这里肯定能获取到)。同样,其他核心组件诸如 response、view、cache等我们都可以通过 Yii::$app->getComponent() 获取,其等价于 Yii::$app->component 的获取形式(component代指response、view等具体某一组件)
设置 @webroot、@web 别名,提前注册这些别名,为后期提供需要
调用 yii\base\Application::bootstrap 方法
yii\base\Application::bootstrap 方法主要是初始化扩展和执行component 的 bootstrap方法,该方法主要用于一些component的启动工作。
yii\base\Application::bootstrap 方法的实现还好,理解起来不是很复杂,详细各位看一下下面的注解内容吧,有任何问题下面留言我们一起交流。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
50
51
52
53
54
55
56
57protected function bootstrap()
{
// yii\web\Application的extensions属性,默认指的是 @vendor/yiisoft/extensions.php 内返回的数组
// extensions.php 是扩展的配置信息,你可以打开看上了解一下
if ($this->extensions === null) {
$file = Yii::getAlias('@vendor/yiisoft/extensions.php');
$this->extensions = is_file($file) ? include($file) : [];
}
// 循环处理extensions,如果扩展存在alias项,则设置别名
// 如果扩展存在bootstrap项,且经过Container容器实例化之后,bootstrap项是 BootstrapInterface 的实例,调用该实例的bootstrap方法执行一些扩展的启动操作
foreach ($this->extensions as $extension) {
if (!empty($extension['alias'])) {
foreach ($extension['alias'] as $name => $path) {
Yii::setAlias($name, $path);
}
}
if (isset($extension['bootstrap'])) {
$component = Yii::createObject($extension['bootstrap']);
if ($component instanceof BootstrapInterface) {
Yii::trace('Bootstrap with ' . get_class($component) . '::bootstrap()', __METHOD__);
$component->bootstrap($this);
} else {
Yii::trace('Bootstrap with ' . get_class($component), __METHOD__);
}
}
}
// bootstrap 属性,默认的,我们在 frontend/config/main.php 和 frontend/config/main-local.php
// 文件内都有配置,是个数组,包括 log、gii、debug三个元素,这里循环处理
foreach ($this->bootstrap as $class) {
$component = null;
// 这里判断bootstrap项是否是字符串,如果是且已经在yii\di\ServiceLocator中注册过,则直接获取
// 如果是yii\base\Module中注册过的module,则直接获取,否则抛出异常
if (is_string($class)) {
if ($this->has($class)) {
$component = $this->get($class);
} elseif ($this->hasModule($class)) {
$component = $this->getModule($class);
} elseif (strpos($class, '\\') === false) {
throw new InvalidConfigException("Unknown bootstrapping component ID: $class");
}
}
if (!isset($component)) {
$component = Yii::createObject($class);
}
// 如果经过以上获取到的 $component 是 yii\base\BootstrapInterface 接口,则trace记录之后,调用 component的bootstrap方法启动component
if ($component instanceof BootstrapInterface) {
Yii::trace('Bootstrap with ' . get_class($component) . '::bootstrap()', __METHOD__);
$component->bootstrap($this);
} else {
Yii::trace('Bootstrap with ' . get_class($component), __METHOD__);
}
}
}
假如说,要为我们的应用添加一项功能,所有来自xxx ip的地址全部禁止访问,我们就可以写一个component,在该component的bootstrap方法内实现对应的业务逻辑,最后把该component ID添加到配置文件的 bootstrap 属性即可。
然后该方法执行完了,下面我们该怎么继续分析呢?想想一下,以上的种种,都是我们从哪里引出的呢?1
(new yii\web\Application($config))->run();
是不是入口文件内的上面这行代码“惹的祸”?没错,就是这一行代码,才有了以上的种种,这有点像悬疑片,得一步步的分析,还不能忘了前因后果。
既然在调用 yii\web\Application::run 方法之前,我们能追踪到的线索都断了,怎么办?
那就继续来看看这个run方法究竟都做了什么,通过路由就能够找到指定的方法执行相应的程序。
但是,执行请求阶段可能要讲的内容很多,所以今天的内容就少一点,下文我们再继续分析吧。