Yii2.0框架分析

加载composer 的自动加载器,支持了PSR-0 PSR-4

1
require(__DIR__ . '/../vendor/autoload.php');

进行常量的定义,并且声明了最基本的方法例如getVersion

1
require __DIR__ . '/BaseYii.php';

加载Yii自己的autoload加载器,从classmap中寻找,指定的类,如果没有找到,会解析名称到路径。

1
2
spl_autoload_register(['Yii', 'autoload'], true, true);
Yii::$classMap = require __DIR__ . '/classes.php';

todo 容器生成

1
Yii::$container = new yii\di\Container();

开始生成一个应用主体,并且加载了config配置,直接运行

1
(new yii\web\Application($config))->run();

主体生成

application is the base class for all web application classes.

Yii::$app 是应用主体的实例,一个请求只会生成一个应用主体
Application类中,定义了初始的defaultRoute,以及coreComponent核心组件的列表,还有一些请求和相应相关的方法

继承链

1
web/application`=>`base/application`=>`base/Model`=>`id/ServiceLocator`=>`base/component`=>`base/object`=>`base/configurable

初始化主体的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
public function __construct($config = [])
{
Yii::$app = $this; // 这样在任何地方都可以通过静态方法的方式,来调用应用主体
static::setInstance($this); // 将请求过来的的类实力,进行保存

$this->state = self::STATE_BEGIN;

$this->preInit($config); // 进行config的初始化,给路径起别名,设置时区等,并且最后加载了核心组件

$this->registerErrorHandler($config); // 注册错误句柄,用来捕捉和处理错误的方法

Component::__construct($config); //
}

其中preInit会进行一个注册核心组件,这里web的入口进行了扩展,包含了request等

1
2
3
4
5
6
7
8
9
10
public function coreComponents()
{
return array_merge(parent::coreComponents(), [
'request' => ['class' => 'yii\web\Request'],
'response' => ['class' => 'yii\web\Response'],
'session' => ['class' => 'yii\web\Session'],
'user' => ['class' => 'yii\web\User'],
'errorHandler' => ['class' => 'yii\web\ErrorHandler'],
]);
}

讲configure格式为对象,存储到应用主体中

1
2
3
4
5
6
7
public function __construct($config = [])
{
if (!empty($config)) {
Yii::configure($this, $config);
}
$this->init();
}

组件注册

这里我们来看下组件是如何注册到应用主体中的,这个-> 实际上调用的是__Set魔术方法,
那我们再看这个$this是什么,很明显是指yii\web\application

1
2
3
4
5
6
7
8
public static function configure($object, $properties)
{
foreach ($properties as $name => $value) {
$object->$name = $value;
}

return $object;
}

我们从webapplication向parent一层一层的找,找到了__set 的定义,在basecomponent

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
public function __set($name, $value)
{
$setter = 'set' . $name;
if (method_exists($this, $setter)) {
// set property
$this->$setter($value);

return;
} elseif (strncmp($name, 'on ', 3) === 0) {
// on event: attach event handler
$this->on(trim(substr($name, 3)), $value);

return;
} elseif (strncmp($name, 'as ', 3) === 0) {
// as behavior: attach behavior
$name = trim(substr($name, 3));
$this->attachBehavior($name, $value instanceof Behavior ? $value : Yii::createObject($value));

return;
}

// behavior property
$this->ensureBehaviors();
foreach ($this->_behaviors as $behavior) {
if ($behavior->canSetProperty($name)) {
$behavior->$name = $value;
return;
}
}

if (method_exists($this, 'get' . $name)) {
throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name);
}

throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name);
}

上面的set方法,会遍历config的属性,来交给不同的set方法处理,果然恰好存在了一个setComponents,用来处理组件

1
2
3
4
5
6
public function setComponents($components)
{
foreach ($components as $id => $component) {
$this->set($id, $component);
}
}

最终组件配置就这样被注册到了$this->_definitions里面,后面我们可以通过$this->get($id) 来获取组件配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public function set($id, $definition)
{
unset($this->_components[$id]);

if ($definition === null) {
unset($this->_definitions[$id]);
return;
}

if (is_object($definition) || is_callable($definition, true)) {
// an object, a class name, or a PHP callable
$this->_definitions[$id] = $definition;
} elseif (is_array($definition)) {
// a configuration array
if (isset($definition['class'])) {
$this->_definitions[$id] = $definition;
} else {
throw new InvalidConfigException("The configuration for the \"$id\" component must contain a \"class\" element.");
}
} else {
throw new InvalidConfigException("Unexpected configuration type for the \"$id\" component: " . gettype($definition));
}

}

注意这个init,并不是当前类下面的init方法,而是被webapplication 覆盖

1
2
3
4
5
public function init()
{
$this->state = self::STATE_INIT;
$this->bootstrap();
}

同样bootstrap方法也被覆盖

1
2
3
4
5
6
7
8
protected function bootstrap()
{
$request = $this->getRequest();
Yii::setAlias('@webroot', dirname($request->getScriptFile()));
Yii::setAlias('@web', $request->getBaseUrl());

parent::bootstrap();
}

看下getRequest是怎么回事,跟踪代码,它通过get方法,来从_definitions中获取request对应的配置
然后根据配置中的yiiwebrequest 来进行创建对象,其中$type 就是配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static function createObject($type, array $params = [])
{
if (is_string($type)) {
return static::$container->get($type, $params);
} elseif (is_array($type) && isset($type['class'])) {
$class = $type['class'];
unset($type['class']);
return static::$container->get($class, $params, $type); // request 走的是这里
} elseif (is_callable($type, true)) {
return static::$container->invoke($type, $params);
} elseif (is_array($type)) {
throw new InvalidConfigException('Object configuration must be an array containing a "class" element.');
}

throw new InvalidConfigException('Unsupported configuration type: ' . gettype($type));
}

进行完web层的引导之后,继续进行base层的引导,包括对配置中bootstrap的引导注册,会直接通过容器得到实例。(具体不详)

小结

在上面的new的过程中,完成了组件的注册,配置的初始化,并且学到了get是createObject通过依赖注入的方法,获取组件对象

请求的处理

前面所有的配置和准备都初始化完毕之后,要进行请求处理了。

这一句的run方法,就是处理请求的整个启动入口

1
$application->run();

使用handleRequest方法来处理请求,并且参数是request实例,其中handleRequest方法要看webapplication层的封装

1
$response = $this->handleRequest($this->getRequest());

调用resolve进行解析

1
list($route, $params) = $request->resolve();

我们再看Urlmanager的时候,发现里面定义的routeParam是r也就是默认接受参数的值(重要)
实际上下面这段代码完成的就是解析了$_GET的值,从里面寻找routeParam 定义的值(r)所代表的内容

1
$result = Yii::$app->getUrlManager()->parseRequest($this);

这里要调用action方法了

1
2
$this->requestedRoute = $route;
$result = $this->runAction($route, $params);

创建控制器+调用action

实际上runAction中的主要内容就是createController的实现:

1
2
3
4
5
6
7
8
9
10
// parts 分为两部分,0 是controller的实例,1 是实例里面的方法名称
$parts = $this->createController($route);
...
...

/* @var $controller Controller */ // 这是一个跟踪优化,不然controller->runaction 就定位不了了
list($controller, $actionID) = $parts;
$oldController = Yii::$app->controller;
Yii::$app->controller = $controller;
$result = $controller->runAction($actionID, $params);

如果遇到了控制器,那么直接返回控制器对象和方法route的名称,这里route类似于action方法名称,代码略微繁琐,但是很清晰,具体实现就是

路由解析规则

例如r=site

  1. 寻找controller,找到site控制器,直接实例化

例如r=site/index

  1. 构造控制器,并且id为site、route为index

例如r=site/index/test

  1. 发现没有找到控制器,那么从模块中获取,这里是重新构造id为site/index route为test,然后调用createControllerById 方法来获取控制器(具体不详,不过应该是通过namespace定位了)

而且createController直接返回了controller的实例

然后我们在createAction中找到解析action方法名称的代码
例如r=site/index-test 那么下面对应的methodName就是actionIndexTest

1
$methodName = 'action' . str_replace(' ', '', ucwords(implode(' ', explode('-', $id))));

内容输出

将调用action方法的值,进行返回,然后直接交给yii\web\Response作为data属性的一部分。最后调用send方法,进行输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
public function send()
{
if ($this->isSent) {
return;
}
$this->trigger(self::EVENT_BEFORE_SEND);
$this->prepare();
$this->trigger(self::EVENT_AFTER_PREPARE);
$this->sendHeaders();
$this->sendContent();
$this->trigger(self::EVENT_AFTER_SEND);
$this->isSent = true;
}

行为是如何注册到组件的呢?

通过attacheBehavior注册行为之后,实际上是添加到了$this_behaviors属性中

那么行为中的属性,就添加到了,_behaviors

进行直接调用行为里面的方法的时候,实际上触发了yii\base\Component里面的__call魔术方法

END

Boss 扫一下呗