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值为空。

至此,总算拿到请求的接口名了,下一篇介绍如何根据接口名找到对应的类与方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值