Yii2底层分析
我是从入口处分析的。 $mysiteRoot/frontend 首先:$mysiteRoot/frontend/index.php
代码语言:javascript复制$application = new yiiwebApplication($config);//先从这入手
$application->run();//先不急,后面会提到
从上面注释的位置入口
$config为配置文件,这里我们来看看是如何加载配置文件内容的。 顺着application我们能找到:yiiwebApplication.php
代码语言:javascript复制class Application extends yiibaseApplication
yiiwebApplication.php中没有构造函数,所以我们顺理成章的找找 它的父类也就是yiibaseApplication,看看父类里面是否有构造函数 yiibaseApplication没有让我们失望, 构造方法如下:
代码语言:javascript复制abstract class Application extends Module{
.....
public function __construct($config = [])
{
Yii::$app = $this;
$this->setInstance($this);//将yiibaseApplication中的所有的属性和方法交给Yii::$app->loadedModules数组中
$this->state = self::STATE_BEGIN;
$this->preInit($config);//加载配置文件的框架信息 如:设置别名,设置框架路径等等 最为重要的是给加载默认组件
$this->registerErrorHandler($config);//加载配置文件中的异常组件
Component::__construct($config);//将配置文件中的所有信息赋值给Object,也就是Yii::$app->配置文件参数可以直接调用配置文件的内容 如:Yii::$app->vendorPath//输出框架路径 Yii::$app->components['redis']//输出redis配置信息
}
......
下面我们来分析下面的代码
首先是:Yii::$app = $this;
这一句指的是,将yiibaseApplication里所有的公共方法都交给了,Yii::$app,其实Yii大部分信息都在Yii::$app变量中 当然也包括它的父类如:yiibaseModule yiidiServiceLocator yiibaseComponent yiibaseObject
代码语言:javascript复制$this->setInstance($this);//module里会用到,为getInstance提供
这一句是指向yiibaseModule
代码语言:javascript复制public static function setInstance($instance)//module模块里会用到,为getInstance提供
{
if ($instance === null) {
unset(Yii::$app->loadedModules[get_called_class()]);
} else {
Yii::$app->loadedModules[get_class($instance)] = $instance;
}
}
这句意思是:将当前类名和存储类的对象变量加入Yii::$app->loadedModules['yiiwebApplication']数组中 这样直接通过Yii::$app->loadedModules['yiiwebApplication']就可以直接调用这个类 重要的用处在于后面的使用如: 在Module里,也就是module使用的时候,可以通过self::getInstance()获取App对象,类似于Yii::$app。这个研究的比较浅,以后再深入,有疑问的童鞋可以深入
代码语言:javascript复制Yii::$app = $this;
$this->setInstance($this);
这两句做的操作是一样的,其实是有所不同的。
代码语言:javascript复制Yii::$app = $this;
指的是通过Yii::$app可以调用yiiwebApplication及其父类所有的方法
代码语言:javascript复制Yii::$app->loadedModules['yiiwebApplication']//也能同样做到
loadedModules是一个数组,存放成员类的。它除了能调用当前,还能调用其它许许多多的类....
代码语言:javascript复制$this->preInit($config);
这一句是将配置文件中的一些变量设置别名,主要是针对路径、URL之类的
代码语言:javascript复制$this->registerErrorHandler($config);
加载异常处理,这块比较深就先不研究了,觉得比较浅的童鞋可以接着补充哈
代码语言:javascript复制Component::__construct($config);
这一句指向Object
代码语言:javascript复制public function __construct($config = [])
{
if (!empty($config)) {
Yii::configure($this, $config);//将配置文件里面的所有配置信息赋值给Object,由于Object是大部分类的基类,实际上也就是交给了yiiwebApplication 您可以Yii::$app->配置参数来访问配置文件中的内容
}
$this->init();//下面会细分析
}
foreach ($this->coreComponents() as $id => $component) {//加载默认组件components
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'];
}
}
这个就是把当前的配置文件config变量中内容交给Object 再就是讲components默认需要加载的组件类,赋到config配置文件变量中。 Object是基础类,所以绝大部分类都能直接调用配置文件中配置内容 如: var_dump(Yii::$app->name); 实际上config文件的数组中有name属性
代码语言:javascript复制return [
'id' => 'app-frontend',
'name' => '环球在线',
......
再回到Object
代码语言:javascript复制public function __construct($config = [])
{
if (!empty($config)) {
Yii::configure($this, $config);
}// 这以上已经执行完了
$this->init();//接下来分析这一句
}
$this->init();
这句实际上执行的是yiibaseModule.php
代码语言:javascript复制/*
取出控制器的命名空间,您也可以理解为路径(* 注:第一次加载它的时候。)
表面看起来没有太多的意义,实则不然,yii2的大部分组件都是以Object为基类的,
所以init函数很重要,控制器、模型、模块module,自定义组件等都可以去实现init方法。
比如说默认的控制器SiteController吧。在里面写一个init方法,当你访问site控制器下任意的$route路径,
都会先执行init方法。作用大不?其它组件同样如此。
*/
public function init()
{
if ($this->controllerNamespace === null) {
$class = get_class($this);
if (($pos = strrpos($class, '\')) !== false) {
$this->controllerNamespace = substr($class, 0, $pos) . '\controllers';
}
}
}
至此第一部分执行完了,再看第二部分吧
代码语言:javascript复制$application = new yiiwebApplication($config);//分析完成
$application->run();//加载主要组件,运行默认控制器
接下拆分 $application->run吧 用ide指向直接到了yiibaseApplication.php
代码语言:javascript复制public function run()
{
try {
$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;
} catch (ExitException $e) {
$this->end($e->statusCode, isset($response) ? $response : null);
return $e->statusCode;
}
}
$response = $this->handleRequest($this->getRequest());
摘出来的这句:
代码语言:javascript复制$this->getRequest()//获取Request对象
这个没啥可说的,获取Request对象
代码语言:javascript复制$this->handleRequest($this->getRequest());
通过指向yiiwebApplication.php
代码语言:javascript复制public function handleRequest($request)
{
if (empty($this->catchAll)) {
list ($route, $params) = $request->resolve();//取出路由及参数
} else {
$route = $this->catchAll[0];
$params = $this->catchAll;
unset($params[0]);
}
try {
Yii::trace("Route requested: '$route'", __METHOD__);
$this->requestedRoute = $route;
$result = $this->runAction($route, $params);//运行控制器中的Acition,下面有详细介绍
if ($result instanceof Response) {
return $result;
} else {
$response = $this->getResponse();
/*这个是加载yiibaseResponse类,在外部可以Yii::$app->get('response')、Yii::$app->getResponse()、Yii::$app->response 等等方式来加载response类,主要用来加载http状态,及头信息,如301,302,404,ajax头等等的获取*/
if ($result !== null) {
$response->data = $result;
}
return $response;
}
} catch (InvalidRouteException $e) {
throw new NotFoundHttpException(Yii::t('yii', 'Page not found.'), $e->getCode(), $e);
}
}
$result = $this->runAction($route, $params);//运行控制器
我们再看看这句指向:yiibaseModule.php
代码语言:javascript复制public function runAction($route, $params = [])
{
$parts = $this->createController($route);//根据路由创建控制器
if (is_array($parts)) {
/* @var $controller Controller */
list($controller, $actionID) = $parts;//获得$actionId和$controller
$oldController = Yii::$app->controller;
Yii::$app->controller = $controller;
$result = $controller->runAction($actionID, $params);//运行使用控制器加载 action方法
Yii::$app->controller = $oldController;//将对象交给Yii::$app->controller 这里面起的作用应该是运行控制器,最后释放控制器的对象变量
return $result;
} else {
$id = $this->getUniqueId();
throw new InvalidRouteException('Unable to resolve the request "' . ($id === '' ? $route : $id . '/' . $route) . '".');
}
}
Module里有一段:
代码语言:javascript复制$controller = Yii::createObject($this->controllerMap[$id], [$id, $this]);
其实在
代码语言:javascript复制$this->createController($route)
这个时候创建了控制器对象
下面看看如何加载action的。
代码语言:javascript复制$result = $controller->runAction($actionID, $params);//运行使用控制器加载 action方法
上面这句指向:
代码语言:javascript复制public function runAction($id, $params = [])
{
$action = $this->createAction($id);//创建action
if ($action === null) {
throw new InvalidRouteException('Unable to resolve the request: ' . $this->getUniqueId() . '/' . $id);
}
Yii::trace("Route to run: " . $action->getUniqueId(), __METHOD__);
if (Yii::$app->requestedAction === null) {
Yii::$app->requestedAction = $action;
}
$oldAction = $this->action;
$this->action = $action;
$modules = [];
$runAction = true;
// 加载默认模块如:Application log等。再调用模块内的beforeAction方法
foreach ($this->getModules() as $module) {
if ($module->beforeAction($action)) {
array_unshift($modules, $module);
} else {
$runAction = false;
break;
}
}
$result = null;
if ($runAction && $this->beforeAction($action)) {//执行beforeAction
// run the action
$result = $action->runWithParams($params);//执行控制器里的action
$result = $this->afterAction($action, $result);//执行beforeAction
// call afterAction on modules
foreach ($modules as $module) {
/* @var $module Module */
$result = $module->afterAction($action, $result);
}
}
$this->action = $oldAction;
return $result;
}
$result = $action->runWithParams($params);//执行控制器里的action
这里才是真正执行action的地方
首先弄清楚$action是什么类?这里可以var_dump一下就清楚了,刚开始我也被编辑器迷糊了找半天 $action类是yiibaseInlineAction
代码语言:javascript复制public function runWithParams($params)
{
$args = $this->controller->bindActionParams($this, $params);//对action的参数进行分析,并且赋值给控制器
Yii::trace('Running action: ' . get_class($this->controller) . '::' . $this->actionMethod . '()', __METHOD__);
if (Yii::$app->requestedParams === null) {
Yii::$app->requestedParams = $args;
}
return call_user_func_array([$this->controller, $this->actionMethod], $args);//用控制器类去执行action方法,并且带上参数。
}
本人使用的ide是phpstorm。顺着index.php的入口一步步读代码,可能有些地方分析不太完整,以后会慢慢补充的。