其实这个大概两年前研究过,不过当时并没有记录下来,现在基本都忘了,所以今个在此记录下。这里借用下当前的一个项目,平时调用接口的url都是这样的:
www.xxxx.com/fund/api/test
现在就来看看yii框架是如何根据 fund/api/test 就找到对应的方法的。从框架入口文件开始:
require(YII_VENDOR_PATH . '/autoload.php');
require(YII_VENDOR_PATH . '/yiisoft/yii2/Yii.php');
$config = require(__DIR__ . '/config/main.php');
(new yii\web\Application($config))->run();
以上四行是index.php文件中部分代码,前三行暂时不管,直接从第四行run()方法调用看起:
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;
}
}
该方法是在 \yii\base\Application 文件中定义的,尽管入口文件中声明的对象是 \yii\web\Application 类,但该类中并没有定义run()方法,而该类又继承了 \yii\base\Application 类,所以就直接调用父类中的run()方法了。该方法主要干了四件事:处理请求前操作、处理请求、处理请求后操作、返回处理后数据。这里主要看是如何处理请求的,也就是这句:
$response = $this->handleRequest($this->getRequest());
注意这里的$this指的还是\yii\web\Application类,handlerRequest()方法也是在该类中定义的:
public function handleRequest($request)
{
if (empty($this->catchAll)) {
try {
list($route, $params) = $request->resolve();
} catch (UrlNormalizerRedirectException $e) {
$url = $e->url;
if (is_array($url)) {
if (isset($url[0])) {
// ensure the route is absolute
$url[0] = '/' . ltrim($url[0], '/');
}
$url += $request->getQueryParams();
}
return $this->getResponse()->redirect(Url::to($url, $e->scheme), $e->statusCode);
}
} 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);
if ($result instanceof Response) {
return $result;
}
$response = $this->getResponse();
if ($result !== null) {
$response->data = $result;
}
return $response;
} catch (InvalidRouteException $e) {
throw new NotFoundHttpException(Yii::t('yii', 'Page not found.'), $e->getCode(), $e);
}
}
从该方法中可看出,路由是从类\yii\web\Request中的resolve()方法中解析出来的:
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()];
}
throw new NotFoundHttpException(Yii::t('yii', 'Page not found.'));
}
这里省略一部分,最终是通过getScriptFile()方法拿到入口文件所在路径:
public function getScriptUrl()
{
if ($this->_scriptUrl === null) {
$scriptFile = $this->getScriptFile();
$scriptName = basename($scriptFile);
if (isset($_SERVER['SCRIPT_NAME']) && basename($_SERVER['SCRIPT_NAME']) === $scriptName) {
$this->_scriptUrl = $_SERVER['SCRIPT_NAME'];
} elseif (isset($_SERVER['PHP_SELF']) && basename($_SERVER['PHP_SELF']) === $scriptName) {
$this->_scriptUrl = $_SERVER['PHP_SELF'];
} elseif (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $scriptName) {
$this->_scriptUrl = $_SERVER['ORIG_SCRIPT_NAME'];
} elseif (isset($_SERVER['PHP_SELF']) && ($pos = strpos($_SERVER['PHP_SELF'], '/' . $scriptName)) !== false) {
$this->_scriptUrl = substr($_SERVER['SCRIPT_NAME'], 0, $pos) . '/' . $scriptName;
} elseif (!empty($_SERVER['DOCUMENT_ROOT']) && strpos($scriptFile, $_SERVER['DOCUMENT_ROOT']) === 0) {
$this->_scriptUrl = str_replace('\\', '/', str_replace($_SERVER['DOCUMENT_ROOT'], '', $scriptFile));
} else {
throw new InvalidConfigException('Unable to determine the entry script URL.');
}
}
return $this->_scriptUrl;
}
//返回入口文件所在路径
public function getScriptFile()
{
if (isset($this->_scriptFile)) {
return $this->_scriptFile;
}
if (isset($_SERVER['SCRIPT_FILENAME'])) {
return $_SERVER['SCRIPT_FILENAME'];
}
throw new InvalidConfigException('Unable to determine the entry script file path.');
}
这里可以看到实际上是通过php的预定义变量$_SERVER拿到的。再经getScriptUrl()方法处理,我这里最终返回的是/fund/index.php。再看getScriptUrl()方法的上一层方法resolvePathInfo():
protected function resolvePathInfo()
{
$pathInfo = $this->getUrl();
if (($pos = strpos($pathInfo, '?')) !== false) {
$pathInfo = substr($pathInfo, 0, $pos);
}
......
$scriptUrl = $this->getScriptUrl();
$baseUrl = $this->getBaseUrl();
if (strpos($pathInfo, $scriptUrl) === 0) {
$pathInfo = substr($pathInfo, strlen($scriptUrl));
} elseif ($baseUrl === '' || strpos($pathInfo, $baseUrl) === 0) {
$pathInfo = substr($pathInfo, strlen($baseUrl));
} elseif (isset($_SERVER['PHP_SELF']) && strpos($_SERVER['PHP_SELF'], $scriptUrl) === 0) {
$pathInfo = substr($_SERVER['PHP_SELF'], strlen($scriptUrl));
} else {
throw new InvalidConfigException('Unable to determine the path info of the current request.');
}
if (substr($pathInfo, 0, 1) === '/') {
$pathInfo = substr($pathInfo, 1);
}
return (string) $pathInfo;
}
该方法我省略了中间一部分,方法开头是通过getUrl()拿到除域名后的所有部分,这里返回的是/fund/api/test。getBaseUrl()方法实际上也是调用了getScriptUrl()方法,该方法这里返回的是/fund,最终通过对字符串进行处理,将结果api/test赋值给$pathinfo并返回。再对返回值做些格式化处理,最终回到\yii\web\application中的handlerRequest()方法中,将api/test赋值给$route,由于该接口是不带参数的请求,所以$params值为空。
至此,总算拿到请求的接口名了,下一篇介绍如何根据接口名找到对应的类与方法。