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<<<<<
    

    









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