Yii2.0源码分析之——控制器文件分析(Controller.php)创建动作、执行动作
在Yii中,当请求一个Url的时候,首先在application中获取request信息,然后由request通过urlManager解析出route,再在Module中根据route来创建controller并处理request。
如:http://www.yiifans.com/index.php?r=site/login。会使用SiteController里面的actionLogin动作来处理这个请求。
Yii中总共有三种控制器类
- base\Controller.php 这个是下面两个的基类
- console\Controller.php 这个是控制台控制器
- web\Controller.php 这个是web控制器
先看看基类base\Controller.php,在基类中大致可分为三个部分
- 和action相关的功能
- 和render相关的功能
- 其它功能
1、和action相关的函数
我们按照这些函数的调用顺序来一一说明
执行路由:public function run($route, $params = [])

/*
* route值即可以为当前controller中的action id,
*
* 也可为module id/controller id/action id/这种格式
* 如果以“/”开头,将于application来处理,否则,用控制器所属模块来处理
*/
public function run($route, $params = [])
{
//先判断route中有没有“/”
$pos = strpos($route, '/');
if ($pos === false) {
//如果没有“/”,则为action id,直接调用runAction来执行这个action。如:index
return $this->runAction($route, $params);
} elseif ($pos > 0) {
//如果“/”在中间,由当前的模块来处理这个route。如:test/index
return $this->module->runAction($route, $params);
} else {
//如果以“/”开头,则用当前的应用程序来处理这个route。如:/test/index;
return Yii::$app->runAction(ltrim($route, '/'), $params);
}
}

执行动作:public function runAction($id, $params = [])

/*
* $id 为action的id,如定义的actionIndex,那么id就为Index。
*
*/
public function runAction($id, $params = [])
{
//创建action
$action = $this->createAction($id);
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; /*
* 获取当前控制器的所以的模块,并执行每个模块的beforeAction来检查当前的action是否可以执行,
* 注意:getModules返回的数组顺序为:从父模块到子模块,
* 所以在执行beforeAction的时候,先检查最外层的父模块,然后检查子模块。
*
* 然而在执行afterAction的时候,顺序就反过来了,先执行子模块,最后执行父模块。
*
*/
foreach ($this->getModules() as $module) {
if ($module->beforeAction($action)) {
array_unshift($modules, $module);
} else {
$runAction = false;
break;
}
} $result = null; //如果所以的父模块都满足执行的条件
if ($runAction) {
/*
* 再判断当前控制器中是beforeAction,
* 最后由生成的action对象来执行runWithParams方法
*
* 执行完后,再执行afterAction方法
*/
if ($this->beforeAction($action)) {
$result = $action->runWithParams($params);
$result = $this->afterAction($action, $result);
}
} //执行所有父模块的afterAction
foreach ($modules as $module) {
/** @var Module $module */
$result = $module->afterAction($action, $result);
} $this->action = $oldAction; return $result;
}

创建动作 public function createAction($id)

//由action id来创建action对象
public function createAction($id)
{
//使用默认的action id ,默认值为:index
if ($id === '') {
$id = $this->defaultAction;
} $actionMap = $this->actions();
if (isset($actionMap[$id])) {
//如果在actions方法中指定了独立的动作,则直接使用此动作。
return Yii::createObject($actionMap[$id], [$id, $this]);
} elseif (preg_match('/^[a-z0-9\\-_]+$/', $id) && strpos($id, '--') === false && trim($id, '-') === $id) {
/*
* action id由:a到z、0到9、\、-、_ 这五种字符组成,
* 并且不能包含“--”
* 并且不能以“-”为开头或结尾
*
* 先以“-”把id分隔为数组,再以“ ”连接到字符串,把每个单词首字母大写,最后把“ ”去掉,并和"action"连接
* 如;
* 1、new-post-v-4
* 2、['new','post','v','4']
* 3、new post v 4
* 4、New Post V 4
* 5、NewPostV4
* 6、actionNewPostV4
*/
$methodName = 'action' . str_replace(' ', '', ucwords(implode(' ', explode('-', $id))));
if (method_exists($this, $methodName)) {
/*
* 如果当前控制器中存在这个actionXXX方法,
* 再通过反射生成方法,再次检查一遍,最后生成InlineAction
*/
$method = new \ReflectionMethod($this, $methodName);
if ($method->getName() === $methodName) {
return new InlineAction($id, $this, $methodName);
}
}
} return null;
}

所以,如果一个动作在定义的时候是用骆驼格式名称的,如actionNewArticle,那么写url的时候r=site/new-article。详情见Yii2.0中文开发向导——控制器(Controller)中的路由部分。
定义独立动作的数组:public function actions()

/*
* 独立action定义
* 这个用来指定独立的action,返回格式为name-value的数组,name为action的id,value为action类的实现,如:
* return [
* 'action1' => 'app\components\Action1',
* 'action2' => [
* 'class' => 'app\components\Action2',
* 'property1' => 'value1',
* 'property2' => 'value2',
* ],
* ];
* 这个主要是用于在子类中重写
*/
public function actions()
{
return [];
}

由createAction可知,当controller在创建action的时候,会根据动作ID先在这个数组里面查找,如果找到则返回这个动作。所以这里定义的动作的优先级要大于在控制器里面定义的actionXXX函数。
绑定动作的参数:public function bindActionParams($action, $params)

/*
* 绑定action的参数。
* 比如定义了动作 actionCrate($id,$name=null)
* 那个这个函数的作用就是从params(一般为$_GET)中提取$id,$name,
*
* 具体的实现在web\Controller.php和console\Controller.php中
*/
public function bindActionParams($action, $params)
{
return [];
}

beforeAction、afterAction,事件触发

//在具体的动作执行之前会先执行beforeAction,如果返回false,则动作将不会被执行,
//后面的afterAction也不会执行(但父模块跌afterAction会执行)
public function beforeAction($action)
{
$event = new ActionEvent($action);
$this->trigger(self::EVENT_BEFORE_ACTION, $event);
return $event->isValid;
} //当前动作执行之后,执行afterAction
public function afterAction($action, $result)
{
$event = new ActionEvent($action);
$event->result = $result;
$this->trigger(self::EVENT_AFTER_ACTION, $event);
return $event->result;
}

在这个都会触发事件,beforeAction触发EVENT_BEFORE_ACTION事件,afterAction触发EVENT_AFTER_ACTION
2、和render相关的功能
关于视图的可以参考Yii2.0中文开发向导——视图(View)
另外对于视图文件是怎么查找的请参考:Yii2.0源码分析之——视图文件分析(View.php)
获取、设置view组件:public function getView()、public function setView($view)

//获取view组件,
public function getView()
{
if ($this->_view === null) {
$this->_view = Yii::$app->getView();
} return $this->_view;
}
//设置view组件
public function setView($view)
{
$this->_view = $view;
}

渲染视图文件和布局文件(如果有布局的话):public function render($view, $params = [])

//渲染视图文件和布局文件(如果有布局的话)
public function render($view, $params = [])
{
//由view对象渲染视图文件
$output = $this->getView()->render($view, $params, $this);
//查找布局文件
$layoutFile = $this->findLayoutFile($this->getView());
if ($layoutFile !== false) {
//由view对象渲染布局文件,
//并把上面的视图结果作为content变量传递到布局中,所以布局中才会有$content变量来表示
return $this->getView()->renderFile($layoutFile, ['content' => $output], $this);
} else {
return $output;
}
}

渲染视图文件,不会应用布局:public function renderPartial($view, $params = [])
//这个只渲染视图文件,不会应用布局
public function renderPartial($view, $params = [])
{
return $this->getView()->render($view, $params, $this);
}
渲染文件:public function renderFile($file, $params = [])
//这个就是用来渲染一个文件,$file为文件实路径或别名路径
public function renderFile($file, $params = [])
{
return $this->getView()->renderFile($file, $params, $this);
}
获取这个控制器对应的view的文件路径:public function getViewPath()
//获取这个控制器对应的view的文件路径,如@app/views/site/xxxx.php
public function getViewPath()
{
return $this->module->getViewPath() . DIRECTORY_SEPARATOR . $this->id;
}
查找布局文件:protected function findLayoutFile($view)

//查找布局文件
protected function findLayoutFile($view)
{
$module = $this->module;
//如果当前控制器设置了布局文件,则直接使用所设置的布局文件
if (is_string($this->layout)) {
$layout = $this->layout;
} elseif ($this->layout === null) {
//如果没有设置布局文件,则查找所有的父模块的布局文件。
while ($module !== null && $module->layout === null) {
$module = $module->module;
}
if ($module !== null && is_string($module->layout)) {
$layout = $module->layout;
}
} //如果没有设置布局文件,返回false
if (!isset($layout)) {
return false;
} /*
* 布局文件有三种路径写法
* 1、以“@”开头,这种会在别名路径中查找布局文件
* 2、以“/”开头,这个会从应用程序的布局文件目录下面查找布局文件
* 3、其它情况, 这个会从当前模块的布局文件目录下查查找布局文件
*/
if (strncmp($layout, '@', 1) === 0) {
$file = Yii::getAlias($layout);
} elseif (strncmp($layout, '/', 1) === 0) {
$file = Yii::$app->getLayoutPath() . DIRECTORY_SEPARATOR . substr($layout, 1);
} else {
$file = $module->getLayoutPath() . DIRECTORY_SEPARATOR . $layout;
} //如果布局文件有文件扩展名,返回
if (pathinfo($file, PATHINFO_EXTENSION) !== '') {
return $file;
}
//加上默认的文件扩展名。
$path = $file . '.' . $view->defaultExtension;
//如果文件不存在,并且,默认的文件扩展名也不是php,则给加上php作为扩展名。
if ($view->defaultExtension !== 'php' && !is_file($path)) {
$path = $file . '.php';
} return $path;
}

3、其它功能
获取当前控制器所有的父模块:public function getModules()

//获取当前控制器所有的父模块
public function getModules()
{
$modules = [$this->module];
$module = $this->module;
while ($module->module !== null) {
//由这里可知,返回的数组顺序为从父模块到子模块
array_unshift($modules, $module->module);
$module = $module->module;
}
return $modules;
}

获取控制器id:public function getUniqueId()

//返回控制器id
public function getUniqueId()
{
//如果当前所属模块为application,则就为该id,否则要前面要加上模块id
return $this->module instanceof Application ? $this->id : $this->module->getUniqueId() . '/' . $this->id;
}

获取路由信息:public function getRoute()
//获取路由信息
public function getRoute()
{
return $this->action !== null ? $this->action->getUniqueId() : $this->getUniqueId();
}
另外还有几个变量和2个事件

//在执行beforeAction方法时触发的事件,
//如果对事件的isValid属性设置为false,将取消action的执行
const EVENT_BEFORE_ACTION = 'beforeAction';
//在执行afterAction方法是触发的事件
const EVENT_AFTER_ACTION = 'afterAction';
//控制器id
public $id;
//所属模块
public $module;
//控制器中默认动作
public $defaultAction = 'index';
//布局文件,如果设置为false,则不使用布局文件
public $layout;
//当前下面执行的action,可在事件中根据这个action来执行不同的操作
public $action;
//视图对象
private $_view;
Yii2.0源码分析之——控制器文件分析(Controller.php)创建动作、执行动作的更多相关文章
- Yii2.0源码阅读-从路由到控制器
之前的文章弄清了一次请求的开始到结束.主要讲了Yii Applicaton实例的创建.初始化,UrlManager如何返回Yii中的路由信息,到runAction,最后将Response发送给客户端. ...
- Yii2.0源码阅读-视图(View)渲染过程
之前的文章我们根据源码的分析,弄清了Yii如何处理一次请求,以及根据解析的路由如何调用控制器中的action,那接下来好奇的可能就是,我在控制器action中执行了return $this->r ...
- Yii2.0源码阅读-一次请求的完整过程
Yii2.0框架源码阅读,从请求发起,到结束的运行步骤 其实最初阅读是从yii\web\UrlManager这个类开始看起,不断的寻找这个类中方法的调用者,最终回到了yii\web\Applicati ...
- Yii2.0源码阅读-behavior的实现原理
Yii2.0中的一个思想就是组件化的思想,所以.大多数的类都直接或间接的继承自yii\base\Component,而组件的三大功能:属性.事件.行为. 行为的目的是为了方便的扩展一个类的功能,而不需 ...
- Caffe源码中caffe.proto文件分析
Caffe源码(caffe version:09868ac , date: 2015.08.15)中有一些重要文件,这里介绍下caffe.proto文件. 在src/caffe/proto目录下有一个 ...
- Yii2.0源码阅读-PHP如何与redis通信?
PHP与Redis可以通过socket进行通信,前提是PHP需要实现Redis的协议 RESP协议描述: 字符串 \r\n : 表示一个正确的状态信息,具体信息是'+'后面的字符(Simple Str ...
- [Android FrameWork 6.0源码学习] Window窗口类分析
了解这一章节,需要先了解LayoutInflater这个工具类,我以前分析过:http://www.cnblogs.com/kezhuang/p/6978783.html Window是Activit ...
- webpack4.0源码解析之esModule打包分析
入口文件index.js采用esModule方式导入模块文件,非入口文件index1.js分别采用CommonJS和esmodule规范进行导出. 首先,init之后创建一个简单的webpack基本的 ...
- [Android FrameWork 6.0源码学习] View的重绘过程之WindowManager的addView方法
博客首页:http://www.cnblogs.com/kezhuang/p/关于Activity的contentView的构建过程,我在我的博客中已经分析过了,不了解的可以去看一下<[Andr ...
随机推荐
- 教你从头到尾利用DQN自动玩flappy bird(全程命令提示,GPU+CPU版)【转】
转自:http://blog.csdn.net/v_JULY_v/article/details/52810219?locationNum=3&fps=1 目录(?)[-] 教你从头到尾利用D ...
- 连接Linux服务器:Win免费SSH客户端工具
连接Linux服务器:Win免费SSH客户端工具 http://blog.csdn.net/jiangdou88/article/details/51585555
- Smith-Waterman算法及其Java实现
Smith-Waterman算法是1981年Smith和Waterman提出的一种用来寻找并比较具有局部相似性区域的动态规划算法,很多后来的算法都是在该算法的基础上发展的.这是一种两序列局部比对算法, ...
- 【bzoj1086】王室联邦
我以为树分块什么的必有高论,结果居然是个暴力…… 方法也很简单,我看下每个节点dfs的时候是否已经大于k个,大于的话我就新开一块. 注意dfs的时候当前节点不能放进子树的块中. #include< ...
- 一点关于"fat model, skinny controller"的思考
导言 想来从事服务器端开发也有将近一年的时间,服务端开发不能忽略的一个架构就是MVC架构,但一开始作为小白的我对这些高大上的概念也是很迷惑,由于很长一段时间应对的业务也是十分简单,业务代码也是流水一样 ...
- 基于iscroll实现下拉和上拉刷新
在原生APP的开发中,有一个常见的功能,就是下拉刷新的功能,这个想必大家都是知道的,但是原生APP的开发,有一个很大的问题就是,你每次更新一些功能,就要用户重新下载一次版本,尤其是在iOS系统中,新版 ...
- 【hdoj_2037】今年暑假不AC
题目:http://acm.hdu.edu.cn/showproblem.php?pid=2037 可以这样理解题意:将每个节目看做是一个区间,起始时间为左右端点,待求的是:最多可以有多少个区间互不相 ...
- 【cocos2d-js官方文档】十二、对象缓冲池
cc.pool的使用场景 经常创建和销毁的元素,例如打飞机游戏里面的子弹等. 不适用的场景:不是很经常创建的物体,比如背景,建筑等. 如何使用cc.pool 让你的类支持cc.pool 首先,你需在需 ...
- Cookie和session的简单理解和应用
一.COOKIE 1.http协议建立连接后,无法保持状态:但实际情况,网站和服务器要进行通讯,需要“保持状态”,因此cookie应运而生:浏览器登陆web服务器后, Web 服务器产生包含有关用户的 ...
- centos6.5 的rpm 可以来这边找
http://copr-be.cloud.fedoraproject.org/results/mosquito/myrepo-el6/epel-6-i386/gcc-4.8.2-16.3.fc20/