2017_01_03_02_路由

路由:http://hostname/index.php?r=site/say&message=Hello+World
当入口脚本在调用 yii\web\Application::run() 方法时,它进行的第一个操作就是解析输入的请求,然后实例化对应的控制器操作处理这个请求。 该过程就被称为引导路由(routing)。

 message 被作为一个参数传给 actionSay() 方法,当省略它时,参数将使用默认值代替。
上面 URL 中的参数 r 代表路由,是整个应用级的, 指向特定操作的独立 ID。
路由格式是 控制器 ID/操作 ID。应用接受请求的时候会检查参数, 使用控制器 ID 去确定哪个控制器应该被用来处理请求。 然后相应控制器将使用操作 ID 去确定哪个操作方法将被用来做具体工作。 上述例子中,路由 site/say 将被解析至 SiteController 控制器和其中的 say 操作。 因此 SiteController::actionSay() 方法将被调用处理请求。

注意:与操作一样,一个应用中控制器同样有唯一的 ID。 控制器 ID 和操作 ID 使用同样的命名规则。 控制器的类名源自于控制器 ID,移除了连字符 ,每个单词首字母大写,并加上 Controller 后缀。 例子:控制器 ID post-comment 相当于控制器类名 PostCommentController。

终端用户通过所谓的路由寻找到操作,路由是包含以下部分的字符串:
模型ID: 仅存在于控制器属于非应用的模块;
控制器ID: 同应用(或同模块如果为模块下的控制器) 下唯一标识控制器的字符串;
操作ID: 同控制器下唯一标识操作的字符串。

路由使用如下格式:
ControllerID/ActionID

如果属于模块下的控制器,使用如下格式:
ModuleID/ControllerID/ActionID

如果用户的请求地址为 http://hostname/index.php?r=site/index, 会执行site 控制器的index 操作。

Yii为开发者提供了路由和URL管理组件:

所谓路由是指URL中用于标识用于处理用户请求的module, controller, action的部分, 一般情况下由 r 查询参数来指定。 如 http://www.digpage.com/index.php?r=post/view&id=100 , 表示这个请求将由PostController 的 actionView来处理。

同时,Yii也提供了一种美化URL的功能,使得上面的URL可以用一个比较整洁、美观的形式表现出来, 如 http://www.digpage.com/post/view/100 。 这个功能的实现是依赖于一个称为 urlManager 的应用组件。

使用 urlManager 开发者可以解析用户的请求,并指派相应的module, controller和action来进行处理, 还可以根据预义的路由规则,生成需要的URL返回给用户使用。 简而言之,urlManger具有解析请求以便确定指派谁来处理请求和根据路由规则生成URL 2个功能。


===================例子===================================
当前只是理解如何使用路由,具体怎么解析和生成路由,是由路由规则决定的


美化URL

一般情况下,Yii应用生成和接受形如 http://www.digpage.com/index.php?r=post/view&id=100 的URL。这个URL分成几个部分:

    表示主机信息的 http://www.digapge.com
    表示入口脚本的 index.php
    表示路由的 r=post/view
    表示普通查询参数的 id=100

其中,主机信息部分从URL来讲,一般是不能少的。当然内部链接可以使用相对路径,这种情况下看似 可以省略,但是User Agent最终发出Request时,也是包含主机信息的。换句话说,Web Server接收并 转交给Yii处理的URL,是完整的、带有主机信息的URL。

而入口脚本 index.php 我们知道,Web Server会将所有的请求都是交由其进行处理。 也就是说,Web Server应当视所有的URL为请求 index.php 脚本。这在 :ref:install 部分我们 已经对Web Server进行过相应配置了。

Yii允许我们不在URL中出现入口脚本 index.php 。

其次,路由信息对于Yii应用而言也必不可少,表明应当使用哪个controller和action来处理请求, 否则Yii只能使用默认的路由来处理请求。这个形式比较固定,采用的是一种类似路径的形式, 一般为 module/controller/action 之类的。

如果将URL省略掉入口脚本,并将路由信息转换成路径,上面的URL就会变成: http://www.digpage.com/post/view?id=100

对于查询参数 id=100 而言,这个URL请求的是编号为100的一个POST, 并执行view操作。那么我们可以再进一步改成 http://www.digpage.com/post/view/100

我们假如所请求的编号100的文章,其标题为 Route , 那么不妨使用用 http://www.digpage.com/post/view/Route 来访问。
这样的话,干脆再加上 .html 好了。 变成 http://www.digpage.com/post/view/Route.html    

Yii有专门的 yii\web\UrlManager 来进行处理,其中:

隐藏入口脚本可以通过 yii\web\UrlManager::showScriptName = false 来实现
路由的路径化可以通过 yii\web\UrlManager::enablePrettyUrl = true 来实现
参数的路径化可以通过路由规则来实现
假后缀(fake suffix) .html 可以通过 yii\web\UrlManager::suffix = '.html' 来实现


路由规则:
路由规则是指 urlManager 用于解析请求或生成URL的规则。 一个路由规则必须实现 yii\web\UrlRuleInterface 接口,这个接口定义了两个方法:

    用于解析请求的 yii\web\UrlRuleInterface::parseRequest()
    用于生成URL的 yii\web\UrlRuleInterface::createUrl()
Yii中,使用 yii\web\UrlRule 来表示路由规则,一般这个类是足够开发者使用的。 但是,如果开发者想自己实现解析请求或生成URL的逻辑,可以以这个类为基类进行派生, 并重载 parseRuquest() 和 createUrl() 。

以下是配置文件中urlManager组件的路由规则配置部分,以几个相对简单、典型的路由规则的为例, 先有个感性认识:
'rules' => [
    // 为路由指定了一个别名,以 post 的复数形式来表示 post/index 路由
    'posts' => 'post/index',

    // id 是命名参数,post/100 形式的URL,其实是 post/view&id=100
    'post/<id:\d+>' => 'post/view',

    // controller action 和 id 以命名参数形式出现
    '<controller:(post|comment)>/<id:\d+>/<action:(create|update|delete)>'
        => '<controller>/<action>',

    // 包含了 HTTP 方法限定,仅限于DELETE方法
    'DELETE <controller:\w+>/<id:\d+>' => '<controller>/delete',

    // 需要将 Web Server 配置成可以接收 *.digpage.com 域名的请求
    'http://<user:\w+>.digpage.com/<lang:\w+>/profile' => 'user/profile',
]

只需大致了解上面这个数组用于为urlManager声明路由规则。
数组的键相当于请求(需要解析的或将要生成的),而元素的值则对应的路由, 即 controller/action 。
请求部分可称为pattern,路由部分则可称为route。


yii\web\UrlRule 的代码:
class UrlRule extends Object implements UrlRuleInterface
{
    // 用于 $mode 表示路由规则的2种工作模式:仅用于解析请求和仅用于生成URL。
    // 任意不为1或2的值均表示两种模式同时适用,
    // 一般未设定或为0时即表示两种模式均适用。
    const PARSING_ONLY = 1;
    const CREATION_ONLY = 2;

    // 路由规则名称
    public $name;

    // 用于解析请求或生成URL的模式,通常是正则表达式
    public $pattern;

    // 用于解析或创建URL时,处理主机信息的部分,如 http://www.digpage.com
    public $host;

    // 指向controller 和 action 的路由
    public $route;

    // 以一组键值对数组指定若干GET参数,在当前规则用于解析请求时,
    // 这些GET参数会被注入到 $_GET 中去
    public $defaults = [];

    // 指定URL的后缀,通常是诸如 ".html" 等,
    // 使得一个URL看起来好像指向一个静态页面。
    // 如果这个值未设定,使用 UrlManager::suffix 的值。
    public $suffix;

    // 指定当前规则适用的HTTP方法,如 GET, POST, DELETE 等。
    // 可以使用数组表示同时适用于多个方法。
    // 如果未设定,表明当前规则适用于所有方法。
    // 当然,这个属性仅在解析请求时有效,在生成URL时是无效的。
    public $verb;

    // 表明当前规则的工作模式,取值可以是 0, PARSING_ONLY, CREATION_ONLY。
    // 未设定时等同于0。
    public $mode;

    // 表明URL中的参数是否需要进行url编码,默认是进行。
    public $encodeParams = true;

    // 用于生成新URL的模板
    private $_template;

    // 一个用于匹配路由部分的正则表达式,用于生成URL
    private $_routeRule;

    // 用于保存一组匹配参数的正则表达式,用于生成URL
    private $_paramRules = [];

    // 保存一组路由中使用的参数
    private $_routeParams = [];

    // 初始化
    public function init() {...}

    // 用于解析请求,由UrlRequestInterface接口要求
    public function parseRequest($manager, $request) {...}

    // 用于生成URL,由UrlRequestInterface接口要求
    public function createUrl($manager, $route, $params) {...}
}

从上面代码看, UrlRule 的属性(可配置项)比较多。

要着重分析一下初始化函数 yii\web\UrlRule::init() ,来加深对这些属性的理解:
public function init()
{
    // 一个路由规则必定要有 pattern ,否则是没有意义的,
    // 一个什么都没规定的规定,要来何用?
    if ($this->pattern === null) {
        throw new InvalidConfigException('UrlRule::pattern must be set.');
    }

    // 不指定规则匹配后所要指派的路由,Yii怎么知道将请求交给谁来处理?
    // 不指定路由,Yii怎么知道这个规则可以为谁创建URL?
    if ($this->route === null) {
        throw new InvalidConfigException('UrlRule::route must be set.');
    }

    // 如果定义了一个或多个verb,说明规则仅适用于特定的HTTP方法。
    // 既然是HTTP方法,那就要全部大写。
    // verb的定义可以是字符串(单一的verb)或数组(单一或多个verb)。
    if ($this->verb !== null) {
        if (is_array($this->verb)) {
            foreach ($this->verb as $i => $verb) {
                $this->verb[$i] = strtoupper($verb);
            }
        } else {
            $this->verb = [strtoupper($this->verb)];
        }
    }

    // 若未指定规则的名称,那么使用最能区别于其他规则的 $pattern
    // 作为规则的名称
    if ($this->name === null) {
        $this->name = $this->pattern;
    }

    // 删除 pattern 两端的 "/",特别是重复的 "/",
    // 在写 pattern 时,虽然有正则的成分,但不需要在两端加上 "/",
    // 更不能加上 "#" 等其他分隔符
    $this->pattern = trim($this->pattern, '/');

    // 如果定义了 host ,将 host 部分加在 pattern 前面,作为新的 pattern
    if ($this->host !== null) {
        // 写入的host末尾如果已经包含有 "/" 则去掉,特别是重复的 "/"
        $this->host = rtrim($this->host, '/');
        $this->pattern = rtrim($this->host . '/' . $this->pattern, '/');

    // 既未定义 host ,pattern 又是空的,那么 pattern 匹配任意字符串。
    // 而基于这个pattern的,用于生成的URL的template就是空的,
    // 意味着使用该规则生成所有URL都是空的。
    // 后续也无需再作其他初始化工作了。
    } elseif ($this->pattern === '') {
        $this->_template = '';
        $this->pattern = '#^$#u';
        return;

    // pattern 不是空串,且包含有 '://',以此认定该pattern包含主机信息
    } elseif (($pos = strpos($this->pattern, '://')) !== false) {
        // 除 '://' 外,第一个 '/' 之前的内容就是主机信息
        if (($pos2 = strpos($this->pattern, '/', $pos + 3)) !== false) {
            $this->host = substr($this->pattern, 0, $pos2);

        // '://' 后再无其他 '/',那么整个 pattern 其实就是主机信息
        } else {
            $this->host = $this->pattern;
        }

    // pattern 不是空串,且不包含主机信息,两端加上 '/' ,形成一个正则
    } else {
        $this->pattern = '/' . $this->pattern . '/';
    }

    // route 也要去掉两头的 '/'
    $this->route = trim($this->route, '/');

    // 从这里往下,请结合流程图来看

    // route 中含有 <参数> ,则将所有参数提取成 [参数 => <参数>]
    // 存入 _routeParams[],
    // 如 ['controller' => '<controller>', 'action' => '<action>'],
    // 留意这里的短路判断,先使用 strpos(),快速排除无需使用正则的情况
    if (strpos($this->route, '<') !== false &&
        preg_match_all('/<(\w+)>/', $this->route, $matches)) {
        foreach ($matches[1] as $name) {
            $this->_routeParams[$name] = "<$name>";
        }
    }

    // 这个 $tr[] 和 $tr2[] 用于字符串的转换
    $tr = [
        '.' => '\\.',
        '*' => '\\*',
        '$' => '\\$',
        '[' => '\\[',
        ']' => '\\]',
        '(' => '\\(',
        ')' => '\\)',
    ];
    $tr2 = [];

    // pattern 中含有 <参数名:参数pattern> ,
    // 其中 ':参数pattern' 部分是可选的。
    if (preg_match_all('/<(\w+):?([^>]+)?>/', $this->pattern, $matches,
        PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) {
        foreach ($matches as $match) {
            // 获取 “参数名”
            $name = $match[1][0];

            // 获取 “参数pattern” ,如果未指定,使用 '[^\/]' ,
            // 表示匹配除 '/' 外的所有字符
            $pattern = isset($match[2][0]) ? $match[2][0] : '[^\/]+';

            // 如果 defaults[] 中有同名参数,
            if (array_key_exists($name, $this->defaults)) {
                // $match[0][0] 是整个 <参数名:参数pattern> 串
                $length = strlen($match[0][0]);
                $offset = $match[0][1];

                // pattern 中 <参数名:参数pattern> 两头都有 '/'
                if ($offset > 1 && $this->pattern[$offset - 1] === '/'
                    && $this->pattern[$offset + $length] === '/') {
                    // 留意这个 (?P<name>pattern) 正则,这是一个命名分组。
                    // 仅冠以一个命名供后续引用,使用上与直接的 (pattern) 没有区别
                    // 见:http://php.net/manual/en/regexp.reference.subpatterns.php
                    $tr["/<$name>"] = "(/(?P<$name>$pattern))?";
                } else {
                    $tr["<$name>"] = "(?P<$name>$pattern)?";
                }

            // defaults[]中没有同名参数
            } else {
                $tr["<$name>"] = "(?P<$name>$pattern)";
            }

            // routeParams[]中有同名参数
            if (isset($this->_routeParams[$name])) {
                $tr2["<$name>"] = "(?P<$name>$pattern)";

            // routeParams[]中没有同名参数,则将 参数pattern 存入 _paramRules[] 中。
            // 留意这里是怎么对  参数pattern  进行处理后再保存的。
            } else {
                $this->_paramRules[$name] = $pattern === '[^\/]+' ? '' :
                    "#^$pattern$#u";
            }
        }
    }

    // 将 pattern 中所有的 <参数名:参数pattern> 替换成 <参数名> 后作为 _template
    $this->_template = preg_replace('/<(\w+):?([^>]+)?>/', '<$1>', $this->pattern);

    // 将 _template 中的特殊字符及字符串使用 tr[] 进行转换,并作为最终的pattern
    $this->pattern = '#^' . trim(strtr($this->_template, $tr), '/') . '$#u';

    // 如果指定了 routePrams 还要使用 tr2[] 对 route 进行转换,
    // 并作为最终的 _routeRule
    if (!empty($this->_routeParams)) {
        $this->_routeRule = '#^' . strtr($this->route, $tr2) . '$#u';
    }
}

    先看init() 的前半部分,这些代码提醒我们:

    规则的 $pattern 和 $route 是必须配置的。
    规则的名称 $name 和主机信息 $host 在未配置的情况下,可以从 $pattern 来获取。
    $pattern 虽然含有正则的成分,但不需要在两端加入 / ,更不能使用 # 等其他分隔符。 Yii会自动为我们加上。
    指定 $pattern 为空串,可以使该规则匹配任意的URL。此时基于该规则所生成的所有URL也都是空串。
    $pattern 中含有 :\\ 时,Yii会认为其中包含了主机信息。此时就不应当再指定 host 。 否则,Yii会将 host 接在这个 pattern 前,作为新的pattern。这会造成该pattern 两段 :\\ , 而这显然不是我们要的。
    
    >>>>待续部分看截图 路由.png<<<<<
    

    









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