Yii2 框架Trace
准备
了解composer的autoload psr0 psr4 加载机制
了解spl_autoload_register
了解依赖注入的实现原理反射
了解常用魔术方法__set,__get,__call
热情与专注
入口分析
加载composer 的自动加载器,支持了PSR-0 PSR-4
require(__DIR__ . '/../vendor/autoload.php');
进行常量的定义,并且声明了最基本的方法例如getVersion
require __DIR__ . '/BaseYii.php';
加载Yii自己的autoload加载器,从classmap中寻找,指定的类,如果没有找到,会解析名称到路径。
spl_autoload_register(['Yii', 'autoload'], true, true);
Yii::$classMap = require __DIR__ . '/classes.php';
todo 容器生成
Yii::$container = new yii\di\Container();
开始生成一个应用主体,并且加载了config配置,直接运行
(new yii\web\Application($config))->run();
主体生成
application is the base class for all web application classes.
Yii::$app
是应用主体的实例,一个请求只会生成一个应用主体
在Application
类中,定义了初始的defaultRoute
,以及coreComponent
核心组件的列表,还有一些请求和相应相关的方法
继承链
web/application
=>base/application
=>base/Model
=>id/ServiceLocator
=>base/component
=>base/object
=>base/configurable
初始化主体的配置
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等
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格式为对象,存储到应用主体中
public function __construct($config = [])
{
if (!empty($config)) {
Yii::configure($this, $config);
}
$this->init();
}
组件注册
这里我们来看下组件是如何注册到应用主体中的,这个->
实际上调用的是__Set
魔术方法,
那我们再看这个$this
是什么,很明显是指yii\web\application
public static function configure($object, $properties)
{
foreach ($properties as $name => $value) {
$object->$name = $value;
}
return $object;
}
我们从webapplication向parent一层一层的找,找到了__set 的定义,在basecomponent
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
,用来处理组件
public function setComponents($components)
{
foreach ($components as $id => $component) {
$this->set($id, $component);
}
}
最终组件配置就这样被注册到了$this->_definitions
里面,后面我们可以通过$this->get($id)
来获取组件配置
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 覆盖
public function init()
{
$this->state = self::STATE_INIT;
$this->bootstrap();
}
同样bootstrap方法也被覆盖
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 就是配置
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方法,就是处理请求的整个启动入口
$application->run();
使用handleRequest方法来处理请求,并且参数是request实例,其中handleRequest方法要看webapplication层的封装
$response = $this->handleRequest($this->getRequest());
调用resolve进行解析
list($route, $params) = $request->resolve();
我们再看Urlmanager的时候,发现里面定义的routeParam是r也就是默认接受参数的值(重要)
实际上下面这段代码完成的就是解析了$_GET的值,从里面寻找routeParam 定义的值(r)所代表的内容
$result = Yii::$app->getUrlManager()->parseRequest($this);
这里要调用action方法了
$this->requestedRoute = $route;
$result = $this->runAction($route, $params);
创建控制器+调用action
实际上runAction中的主要内容就是createController
的实现:
// 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
寻找controller,找到site控制器,直接实例化
例如r=site/index
构造控制器,并且id为site、route为index
例如r=site/index/test
发现没有找到控制器,那么从模块中获取,这里是重新构造id为site/index route为test,然后调用
createControllerById
方法来获取控制器(具体不详,不过应该是通过namespace定位了)
而且createController
直接返回了controller的实例
然后我们在createAction
中找到解析action方法名称的代码
例如r=site/index-test 那么下面对应的methodName就是actionIndexTest
$methodName = 'action' . str_replace(' ', '', ucwords(implode(' ', explode('-', $id))));
内容输出
将调用action方法的值,进行返回,然后直接交给yii\web\Response
作为data属性的一部分。最后调用send方法,进行输出。
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
魔术方法