yii框架路由解析(一)

其實這個大概兩年前研究過,不過當時並沒有記錄下來,現在基本都忘了,所以今個在此記錄下。這裏借用下當前的一個項目,平時調用接口的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/testgetBaseUrl()方法實際上也是調用了getScriptUrl()方法,該方法這裏返回的是/fund,最終通過對字符串進行處理,將結果api/test賦值給$pathinfo並返回。再對返回值做些格式化處理,最終回到\yii\web\application中的handlerRequest()方法中,將api/test賦值給$route,由於該接口是不帶參數的請求,所以$params值爲空。

至此,總算拿到請求的接口名了,下一篇介紹如何根據接口名找到對應的類與方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章