Yii2.0源码阅读-一次请求的完整过程
Yii2.0框架源码阅读,从请求发起,到结束的运行步骤
其实最初阅读是从yii\web\UrlManager
这个类开始看起,不断的寻找这个类中方法的调用者,最终回到了yii\web\Application
,那就从头开始看。
1、Nginx
nginx作为web服务器,时刻监听着80端口,等待接收用户请求,并转发给php进行处理,Yii2.0框架使用了统一的入口脚本:index.php
所以nginx中有如下的配置:
location / {
try_files $uri $uri/ /index.php?$args;
}
首先nginx会检索$uri
与$uri/
这个路径下的文件,如果没有找到,那就将请求交给index.php
2、创建Yii Aapplication 实例
从index.php
文件可以看出加载了各个文件夹下的配置项,然后new
了一个application,这个构造方法是在yii\web\Application
的父类yii\base\Applicaton
中,主要根据配置项初始化:
public function __construct($config = [])
{
Yii::$app = $this;
static::setInstance($this);
$this->state = self::STATE_BEGIN;
$this->preInit($config);
$this->registerErrorHandler($config);
Component::__construct($config);
}
3、Application run
可以看到创建实例之后调用了run方法$applicaton->run();
run方法位于yii\base\Application
中,总的来说就是执行:
- before request 处理请求前的操作
- handle request 真正的处理这次HTTP请求
- after request 请求处理完成之后的操作
- send response 将响应信息发送给客户端
public function run()
{
//代码简化 try catch去掉
$this->state = self::STATE_BEFORE_REQUEST;
$this->trigger(self::EVENT_BEFORE_REQUEST);
$this->state = self::STATE_HANDLING_REQUEST;
$response = $this->handleRequest($this->getRequest());
$this->state = self::STATE_AFTER_REQUEST;
$this->trigger(self::EVENT_AFTER_REQUEST);
$this->state = self::STATE_SENDING_RESPONSE;
$response->send();
$this->state = self::STATE_END;
return $response->exitStatus;
}
4、Get Request Object
请求的处理从 $this->handleRequest($this->getRequest());
开始,首先看handleRequest的参数是getRequest的返回值,getRequest方法位于yii\web\Application
中:
public function getRequest()
{
return $this->get('request');
}
get方法,根据层层的继承关系找到其位于yii\base\Application
的父类yii\base\Module
的父类yii\di\ServiceLocator
中:
public function get($id, $throwException = true)
{
if (isset($this->_components[$id])) {
return $this->_components[$id];
}
if (isset($this->_definitions[$id])) {
$definition = $this->_definitions[$id];
if (is_object($definition) && !$definition instanceof Closure) {
return $this->_components[$id] = $definition;
} else {
return $this->_components[$id] = Yii::createObject($definition);
}
} elseif ($throwException) {
throw new InvalidConfigException("Unknown component ID: $id");
} else {
return null;
}
}
这里我们先不考虑$_definitions $_components
这些变量初始化的位置,打印$id='request'
是获取到的$definition
为:
array(2) {
["cookieValidationKey"]=>
string(32) "0drrX5wQ2wZ8Hli2Ql48ss8efcE-W11m"
["class"]=>
string(15) "yii\web\Request"
}
说一下这个结果的由来,事实上这个就是我们在config/main-local.php中的配置项:
$config = [
'components' => [
'request' => [
'cookieValidationKey' => '0drrX5wQ2wZ8Hli2Ql48ss8efcE-W11m',
],
],
];
而我们获取到信息中有class=>yii\web\Request
,这个class的添加是在new Application的时候,也就在构造方法中执行了preInit()
,preInit中对核心的组件进行了初始化,添加了class信息,然后更新到了config数组中。
//preInit
//coreComponents 在yii\web\Application 与 yii\base\Application中
foreach ($this->coreComponents() as $id => $component) {
if (!isset($config['components'][$id])) {
$config['components'][$id] = $component;
} elseif (is_array($config['components'][$id]) && !isset($config['components'][$id]['class'])) {
$config['components'][$id]['class'] = $component['class'];
}
}
回到上面ServiceLocator的get
方法中,我们获取到了$definition
,现在是一个数组并非一个对象,所以执行的是:return $this->_components[$id] = Yii::createObject($definition);
return回来的就是Request对象: object(yii\web\Request)
这个createObject方法,看注释中的描述就是可以理解为一个高级版本的
new
,因为可以根据字符串(类名),数组(含class信息),匿名函数来创建一个对象,然后返回。
5、Handle Request
run中调用的handleRequest()
位于yii\web\Application
中,这里主要的操作就是:
- 从Request中获取用户请求路由
- 调用这个路由对应的action
// @param $request yii\web\Request
public function handleRequest($request)
{
if (empty($this->catchAll)) {
list ($route, $params) = $request->resolve();
} else {
$route = $this->catchAll[0];
$params = $this->catchAll;
unset($params[0]);
}
//下面暂时省略
}
catchAll用于系统维护的时候,将所有的请求转发到一处进行处理,默认值为空,所以这里的条件判断进入了list ($route, $params) = $request->resolve();
resolve肯定是在yii\web\Request
中了:
public function resolve()
{
$result = Yii::$app->getUrlManager()->parseRequest($this);
if ($result !== false) {
list ($route, $params) = $result;
if ($this->_queryParams === null) {
$_GET = $params + $_GET; // preserve numeric keys
} else {
$this->_queryParams = $params + $this->_queryParams;
}
return [$route, $this->getQueryParams()];
} else {
throw new NotFoundHttpException(Yii::t('yii', 'Page not found.'));
}
}
这里通过调用yii\web\UrlManager
来解析请求路由,parseRequest方法的参数为Request对象,它的主要任务是:
- 通过Request对象获取path info
- 检查是否有跟我们配置的url rule匹配的,有则返回
- 没有则直接按照默认的方式进行解析
进入UrlManager:
public function parseRequest($request)
{
//在enable pretty url的前提下
$pathInfo = $request->getPathInfo();
//如果在rules中匹配到了 request 直接返回转换后的路由
foreach ($this->rules as $rule) {
if (($result = $rule->parseRequest($this, $request)) !== false) {
return $result;
}
}
//没有则使用默认方式,同时对路由进行检查,判断是否有多与一个的斜线
//判断是否使用了.html后缀
//代码省略,这里做的就是比较和截取的操作
return [$pathInfo, []];
}
getPathInfo()
先不展开讨论,主要就是从当前请求的http header中获取path info。
回到yii\web\Application
的handleRequest中:
public function handleRequest($request)
{
//代码简化一下,完整版请查看yii\web\Application类
list ($route, $params) = $request->resolve();
$this->requestedRoute = $route;
//主要操作
$result = $this->runAction($route, $params);
if ($result instanceof Response) {
return $result;
} else {
$response = $this->getResponse();
if ($result !== null) {
$response->data = $result;
}
return $response;
}
}
在获取到属于Yii中的路由(route)信息之后,接下来的主要操作就是执行对应的controller中的action,或者是对应模块(module)中的controller,controller中对应的action。runAction($route,$params)
先不展开讨论,主要知道执行完成之后我们获取的是一个yii\web\Response对象就行了。
6、回到run方法
将response信息发送给客户端
// yii\base\Application run()
$response = $this->handleRequest($this->getRequest());
$response->send();
return $response->exitStatus;
至此一次完整的请求完成。