Yii应用的入口脚本最后一句启动了WebApplication
Yii::createWebApplication($config)->run();
CApplication:
1.
public
function
run()
2.
{
3.
$this
->onBeginRequest(
new
CEvent(
$this
));
4.
$this
->processRequest();
5.
$this
->onEndRequest(
new
CEvent(
$this
));
6.
}
processRequest()开始处理请求,由CWebApplication实现:
01.
public
function
processRequest()
02.
{
03.
if
(
is_array
(
$this
->catchAllRequest) && isset(
$this
->catchAllRequest[0]))
04.
{
05.
$route
=
$this
->catchAllRequest[0];
06.
foreach
(
array_splice
(
$this
->catchAllRequest,1)
as
$name
=>
$value
)
07.
$_GET
[
$name
]=
$value
;
08.
}
09.
else
10.
$route
=
$this
->getUrlManager()->parseUrl(
$this
->getRequest());
11.
$this
->runController(
$route
);
12.
}
urlManager应用组件的parseUrl() 创建了$route (形式为controllerID/actionID的字符串),runController()创建Controller对象开始处理http请求。
$route 的值可能存在以下几种情况:
- 为空: 用 defaultController 值代替;
- “moduleID/controllerID/actionID”: module下的
- “controllerID/actionID” : 最常见的形式
- “folder1/folder2/controllerID/actionID” 多级目录下的控制器
runController首先调用createController()创建控制器对象
01.
public
function
runController(
$route
)
02.
{
03.
//根据route创建Controller对象数组
04.
if
((
$ca
=
$this
->createController(
$route
))!==null)
05.
{
06.
//包含controller对象和actionID
07.
list(
$controller
,
$actionID
)=
$ca
;
08.
//TODO::这里是干什么用的
09.
$oldController
=
$this
->_controller;
10.
$this
->_controller=
$controller
;
11.
//调用controller对象的初始化方法
12.
$controller
->init();
13.
//使用actionID运行这个Controller
14.
$controller
->run(
$actionID
);
15.
$this
->_controller=
$oldController
;
16.
}
17.
Else
18.
//如果没有找到对应的Controller,跳转到404页面
19.
throw
new
CHttpException(404,Yii::t(
'yii'
,
'Unable to resolve the request "{route}".'
,
20.
array
(
'{route}'
=>
$route
===
''
?
$this
->defaultController:
$route
)));
21.
}
其实真正的核心处理是在createController,对于createController,我们着重需要了解的是下面的这段注释:
01.
/**
02.
* ……
03.
* 这个方法以下面的顺序创建一个控制器
04.
* 1. 如果第一个字段在controllerMap(初始配置)中,则使用对应的控制器配置来创建控制器
05.
* 2.如果第一个字段是一个模块(module)ID,则使用相应的模块来创建控制器
06.
* 3.如果通过上面两项均无法创建控制器,将会搜索controllerPath(根目录对应的controller文件夹)来创建对应的控制器。
07.
* ……
08.
*/
09.
public
function
createController(
$route
,
$owner
=null)
10.
{
11.
// $owner为空则设置为$this,即 $_app对象
12.
if
(
$owner
===null)
13.
$owner
=
$this
;
14.
// $route为空设置为defaultController,在$config里配置
15.
if
((
$route
=trim(
$route
,
'/'
))===
''
)
16.
$route
=
$owner
->defaultController;
17.
$caseSensitive
=
$this
->getUrlManager()->caseSensitive;
18.
19.
$route
.=
'/'
;
20.
// 逐一取出 $route 按 ‘/’分割后的第一段进行处理
21.
while
((
$pos
=
strpos
(
$route
,
'/'
))!==false)
22.
{
23.
// $id 里存放的是 $route 第一个 ‘/’前的部分
24.
$id
=
substr
(
$route
,0,
$pos
);
25.
if
(!preg_match(
'/^\w+$/'
,
$id
))
26.
return
null;
27.
if
(!
$caseSensitive
)
28.
$id
=
strtolower
(
$id
);
29.
// $route 存放’/’后面部分
30.
$route
=(string)
substr
(
$route
,
$pos
+1);
31.
if
(!isset(
$basePath
))
// 完整$route的第一段
32.
{
33.
// 如果$id在controllerMap[]里做了映射
34.
// 直接根据$id创建controller对象
35.
if
(isset(
$owner
->controllerMap[
$id
]))
36.
{
37.
return
array
(
38.
Yii::createComponent(
$owner
->controllerMap[
$id
],
$id
,
$owner
===
$this
?null:
$owner
),
39.
$this
->parseActionParams(
$route
),
40.
);
41.
}
42.
43.
// $id 是系统已定义的 module,根据$id取得module对象作为$owner参数来createController
44.
if
((
$module
=
$owner
->getModule(
$id
))!==null)
45.
return
$this
->createController(
$route
,
$module
);
46.
// 控制器所在的目录
47.
$basePath
=
$owner
->getControllerPath();
48.
$controllerID
=
''
;
49.
}
50.
else
51.
$controllerID
.=
'/'
;
52.
$className
=ucfirst(
$id
).
'Controller'
;
53.
$classFile
=
$basePath
.DIRECTORY_SEPARATOR.
$className
.
'.php'
;
54.
// 控制器类文件存在,则require并创建控制器对象&返回
55.
if
(
is_file
(
$classFile
))
56.
{
57.
if
(!
class_exists
(
$className
,false))
58.
require
(
$classFile
);
59.
if
(
class_exists
(
$className
,false) &&
is_subclass_of
(
$className
,
'CController'
))
60.
{
61.
$id
[0]=
strtolower
(
$id
[0]);
62.
return
array
(
63.
new
$className
(
$controllerID
.
$id
,
$owner
===
$this
?null:
$owner
),
64.
$this
->parseActionParams(
$route
),
65.
);
66.
}
67.
return
null;
68.
}
69.
// 未找到控制器类文件,可能是多级目录,继续往子目录搜索
70.
$controllerID
.=
$id
;
71.
$basePath
.=DIRECTORY_SEPARATOR.
$id
;
72.
}
73.
}
也就是说,对于一个aaaa/bbbb/cccc的路由,yii首先从config/main.php中定义的controllerMap去寻找是否有名为aaaa的controller,如果有,那么就已aaaa为controller进行创建,否则再去寻找是否有名为aaaa的模块,如果有,那么就使用aaaa模块的名为bbbb的controller进行创建,否则在protected/controllers下寻找是否有名为aaaa的controller。
createController() 返回一个创建好的控制器对象和actionID, runController()调用控制器的init()方法和run($actionID)来运行控制器:
$controller->init()里没有动作,因此我们可以在自己的控制器中重写这个方法来实现初始化的时候处理数据。
run()方法:
01.
public
function
run(
$actionID
)
02.
{
03.
if
((
$action
=
$this
->createAction(
$actionID
))!==null)
04.
{
05.
if
((
$parent
=
$this
->getModule())===null)
06.
$parent
=Yii::app();
07.
if
(
$parent
->beforeControllerAction(
$this
,
$action
))
08.
{
09.
$this
->runActionWithFilters(
$action
,
$this
->filters());
10.
$parent
->afterControllerAction(
$this
,
$action
);
11.
}
12.
}
13.
else
14.
$this
->missingAction(
$actionID
);
15.
}
$controller->run($actionID)里首先创建了Action对象:
01.
public
function
createAction(
$actionID
)
02.
{
03.
// 为空设置为defaultAction
04.
if
(
$actionID
===
''
)
05.
$actionID
=
$this
->defaultAction;
06.
// 控制器里存在 'action'.$actionID 的方法,创建CInlineAction对象
07.
if
(method_exists(
$this
,
'action'
.
$actionID
) &&
strcasecmp
(
$actionID
,
's'
))
// we have actions method
08.
return
new
CInlineAction(
$this
,
$actionID
);
09.
// 否则根据actions映射来创建Action对象(就是我们自己创建的继承自CAtion的对象)
10.
else
11.
return
$this
->createActionFromMap(
$this
->actions(),
$actionID
,
$actionID
);
12.
}
这里可以看到控制器并不是直接调用了action方法,而是需要一个Action对象来运行控制器动作,这样就统一了控制器方法和actions映射的action对象对action的处理,即两种形式的action处理都统一为IAction接口的run()调用。
IAction接口要求实现run(),getId(),getController () 三个方法,Yii提供的CAction类要求构造函数提供Controller和Id并实现了getId()和getController ()的处理,Action类从CAction继承即可。
这里其实可以分为两种action,上面的注释也写了,第一种就是我们在控制器中定义了相关的方法的比如:actionIndex这种,这种的话系统是调用CInlineAction对象来处理的。还有一种呢就是我们没有定义
实际的方法的而是定在actions方法中的,也就是我们自定义了一个类的那种,这种的话就是直接调用我们的这个class来处理了。不管是哪一种,都必须要实现一个run方法,因为这个方法是来具体实现
业务的,如系统的run方法就调用了我们控制器的方法。其实在CInlineAction类中还有个方法就是runWithParams方法,这个方法其实是重写了CAtion类中的一个方法,其实框架默认调用的就是这个方法
而不是run方法,这个方法会判断run方法的参数情况然后再来处理,但是不管参数情况如何都会调用这个run方法,因此我们在我们自定义的ACTION类中也可以实现这个runWithParams方法的。
CInlineAction在web/action下,run()是很简单的处理过程,调用了Controller的action方法:
01.
class
CInlineAction
extends
CAction
02.
{
03.
public
function
run()
04.
{
05.
$method
=
'action'
.
$this
->getId();
06.
$this
->getController()->
$method
();
07.
}
08.
public
function
runWithParams(
$params
)
09.
{
10.
$methodName
=
'action'
.
$this
->getId();
11.
$controller
=
$this
->getController();
12.
$method
=
new
ReflectionMethod(
$controller
,
$methodName
);
13.
//如果run方法有参数的话,那么就用反射来处理,最后调用run方法
14.
if
(
$method
->getNumberOfParameters()>0)
15.
return
$this
->runWithParamsInternal(
$controller
,
$method
,
$params
);
16.
//直接调用我们action
17.
else
18.
return
$controller
->
$methodName
();
19.
}
20.
}
这是CAtion中的runWithParams方法,上面的CInlineAction重写CAtion中的此方法
01.
public
function
runWithParams(
$params
)
02.
{
03.
$method
=
new
ReflectionMethod(
$this
,
'run'
);
04.
//如果run方法有参数的话,那么就用反射来处理,最后调用run方法
05.
if
(
$method
->getNumberOfParameters()>0)
06.
return
$this
->runWithParamsInternal(
$this
,
$method
,
$params
);
07.
//直接调用CInlineAction的run方法
08.
else
09.
return
$this
->run();
10.
}
回到 $controller->run($actionID)
01.
public
function
run(
$actionID
)
02.
{
03.
if
((
$action
=
$this
->createAction(
$actionID
))!==null)
04.
{
05.
if
((
$parent
=
$this
->getModule())===null)
06.
$parent
=Yii::app();
07.
if
(
$parent
->beforeControllerAction(
$this
,
$action
))
08.
{
09.
$this
->runActionWithFilters(
$action
,
$this
->filters());
10.
$parent
->afterControllerAction(
$this
,
$action
);
11.
}
12.
}
13.
else
14.
$this
->missingAction(
$actionID
);
Yii::app()->beforeControllerAction() 实际是固定返回true的,所以action对象实际是通过控制器的runActionWithFilters()被run的
01.
public
function
runActionWithFilters(
$action
,
$filters
)
02.
{
03.
// 控制器里没有设置过滤器
04.
if
(
empty
(
$filters
))
05.
$this
->runAction(
$action
);
06.
// 控制器里设置过滤器
07.
else
08.
{
09.
$priorAction
=
$this
->_action;
10.
$this
->_action=
$action
;
11.
// 创建过滤器链对象并运行,其实这一步的执行很简单,就是按照过滤规则先过滤一下,然后再来执行runAction($action)方法
12.
CFilterChain::create(
$this
,
$action
,
$filters
)->run();
13.
$this
->_action=
$priorAction
;
14.
}
15.
}
没有过滤器,runAction()就是最终要调用前面创建的action对象的runWithParams(),然后在这个方法中决定是访问run还是$methodName还是其他,因为这个方法可以被用户重写。
01.
public
function
runAction(
$action
)
02.
{
03.
$priorAction
=
$this
->_action;
04.
$this
->_action=
$action
;
05.
if
(
$this
->beforeAction(
$action
))
06.
{
07.
if
(
$action
->runWithParams(
$this
->getActionParams())===false)
08.
$this
->invalidActionParams(
$action
);
09.
else
10.
$this
->afterAction(
$action
);
11.
}
12.
$this
->_action=
$priorAction
;
13.
}
在runWithParams()中调用了我们真正的方法:
01.
public
function
runWithParams(
$params
)
02.
{
03.
$methodName
=
'action'
.
$this
->getId();
04.
$controller
=
$this
->getController();
05.
$method
=
new
ReflectionMethod(
$controller
,
$methodName
);
06.
if
(
$method
->getNumberOfParameters()>0)
07.
return
$this
->runWithParamsInternal(
$controller
,
$method
,
$params
);
08.
else
09.
return
$controller
->
$methodName
();
10.
}
其实在CController类中的run方法并没有加上final关键字,意味着我们这控制器中可以重写他,然后来实现我们自己从控制器到方法的这么一段流程,当然YII已经实现的很perfect了。
这样子,然后联系我们上一篇讲的,我们的程序就已经讲解到了我们自己的控制器了,下一篇将分析执行控制器过程中的过滤和视图这一块的功能。