其實這個大概兩年前研究過,不過當時並沒有記錄下來,現在基本都忘了,所以今個在此記錄下。這裏借用下當前的一個項目,平時調用接口的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值爲空。
至此,總算拿到請求的接口名了,下一篇介紹如何根據接口名找到對應的類與方法。