继上篇yii框架路由解析(一)拿到调用接口后,接着看yii框架是如何处理的。还是从handlerRequest()方法看起:
public function handleRequest($request)
{
list($route, $params) = $request->resolve();
$this->requestedRoute = $route;
$result = $this->runAction($route, $params);
return $result;
}
掐头去尾以及省去中间部分代码后,方法体就剩下以上这些。现在$route的值为api/test,$params值为空,很明显,是通过runAction()方法对其进行处理并返回最终结果的。
public function runAction($route, $params = [])
{
$parts = $this->createController($route);
list($controller, $actionID) = $parts;
$oldController = Yii::$app->controller;
Yii::$app->controller = $controller;
$result = $controller->runAction($actionID, $params);
if ($oldController !== null) {
Yii::$app->controller = $oldController;
}
return $result;
}
在方法一开始就调用了另一个方法createController():
public function createController($route)
{
if (strpos($route, '/') !== false) {
list($id, $route) = explode('/', $route, 2);
} else {
$id = $route;
$route = '';
}
...
$controller = $this->createControllerByID($id);
if ($controller === null && $route !== '') {
$controller = $this->createControllerByID($id . '/' . $route);
$route = '';
}
return $controller === null ? false : [$controller, $route];
}
这里还是省略了方法体中的一部分,该方法前面一部分是将传入的参数$route分为两部分,api赋值给$id,test赋值给$route,最终通过createControllerByID()方法找到在api路径下定义的所有接口,然后在给出的所有接口中找出当前调用的接口。最主要的是该方法生成了ApiController类对象,并赋值给$controller返回到上一层。回到上一层runAction()方法后,将当前调用接口名test赋值给$actionID,并作为参数,调用\yii\base\controller类中的runAction()方法。
public function runAction($id, $params = [])
{
$action = $this->createAction($id);
$result = null;
if ($this->beforeAction($action)) {
// run the action
$result = $action->runWithParams($params);
$result = $this->afterAction($action, $result);
foreach ($modules as $module) {
$result = $module->afterAction($action, $result);
}
}
return $result;
}
在该方法一开始调用createAction()方法以接口名作为参数生成了对应的接口类对象:
public function createAction($id)
{
$actionMap = $this->actions();
if (isset($actionMap[$id])) {
return Yii::createObject($actionMap[$id], [$id, $this]);
} elseif (preg_match('/^[a-z0-9\\-_]+$/', $id) && strpos($id, '--') === false && trim($id, '-') === $id) {
$methodName = 'action' . str_replace(' ', '', ucwords(implode(' ', explode('-', $id))));
if (method_exists($this, $methodName)) {
$method = new \ReflectionMethod($this, $methodName);
if ($method->isPublic() && $method->getName() === $methodName) {
return new InlineAction($id, $this, $methodName);
}
}
}
return null;
}
在该方法中调用了ApiController中定义的actions()方法,该方法获取到的就是每个接口在项目中存储路径,以接口名作为数组的键,这样就可以通过接口名直接找到接口所在路径了。并通过createObject()方法生成TestAction对象,这里目前只需知道yii框架是通过createObject()方法根据接口名生成对应类对象即可,本章暂时不对该方法详细介绍。生成接口名对应类对象后,开始调用\yii\base\action中的runWithParams()方法对其进行处理,这里之所以能够调用是因为定义的每个接口都继承了类Action。
use app\components\BaseAction;
class TestAction extends BaseAction
{
}
use yii\base\Action;
class BaseAction extends Action
{
}
再回到调用的runWithParams()方法中来:
//主要代码如下,省略一部分
public function runWithParams($params)
{
$args = $this->controller->bindActionParams($this, $params);
$result = call_user_func_array([$this, 'run'], $args);
return $result;
}
这里面的$this->controller是什么东西呢?就是之前在调用createController方法时生成的ApiController类对象,是在构造函数中赋过去的。最终通过回调函数call_user_func_array()执行该接口对应的方法了,此时如果打印$result的值,就可以看到接口的返回值了。
另外,看这个回调函数的参数调用的run()方法,而在接口中是没有定义这个方法的,这是因为在接口继承的父类BaseAction中实现了该方法。
public function run()
{
$handler = Yii::$app->controller->id;
$this->$handler();
}
这里的id值也就是api了,所以要在每个接口中定义api()方法供调用了。