今天啃關於路由解析的部分,感受這塊仍是挺複雜的;有的點仍是沒看透,把看明白的總結出來。php
咱們在使用路由解析的時候,不少部分參與了路由解析,遠不止tp框架以下圖
能夠看出從客戶端發起到服務器處理響應,經理的4個階段,tp框架只是其中一部分。html
url做爲一種輸入的數據,過路由解析,
匹配到應用業務控制器(也有多是閉包函數和自定義的類)thinkphp
path_info字符串標誌,path_info兼容內容,path_info分隔符
'var_pathinfo' => 's',
'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'],
'pathinfo_depr' => '/',
系統變量request_uri,系統變量base_url
'url_request_uri' => 'REQUEST_URI',
'base_url' => $_SERVER["SCRIPT_NAME"],
僞靜態後綴,普通方式參數?,禁止訪問後綴
'url_html_suffix' => '.html',
'url_common_param' => false,
'url_deny_suffix' => 'ico|png|gif|jpg',數組
路由開啓與關閉,強制路由開啓與關閉,模塊映射
'url_route_on' => true,
'url_route_must' => false,
'url_module_map' => [],
域名部署開啓與關閉,根域名
'url_domain_deploy' => false,
'url_domain_root' => '',
控制器自動轉換開啓與關閉,操做自動轉換開啓與關閉
'url_controller_convert' => true,
'url_action_convert' => true,
// 按照順序解析變量
'url_param_type' => 1,
//路由配置文件,可配置多個
'route_config_file' => ['route'],緩存
這裏只簡單舉兩個栗子,想知道更詳細請查閱thinkphp5.0官方手冊畢竟這裏咱們是以分析源碼爲主。
服務器
我將tp5的路由解析是將咱們配置文件中配置的路由規則註冊進thinkRoute.php的私有靜態變量$rules,本質上來講檢驗路由就是按規則將$rules中的信息進行校驗, 因此爲了便於理解,我將講述分爲兩部分, 一部分是路由註冊, 一部分是路由解析;下面將會根據這兩部分進行分析。閉包
整個過程以下圖
入口
app::run==>app::routeCheck===>route::import
在routeCheck中會檢查是否開啓路由緩存,同時會去RUNTIME_PATH下查找是否有緩存的路由文件,若是沒有引入文入路由文件,在本例中便是route .php 當引入app
會執行其中標紅的部分,返回的數組,會執行Route::import 方法,而在註冊路由的過程當中除了分組路由之外(咱們另外分析),最後都是對Route::setRule的封裝,因此主要分析一下setRule
首先介紹一下Route的$rules的結構
其中get到options位置都是類似的,一個整的註冊相似與
框架
rule 爲路由表達式 route爲匹配路由路徑 var 爲指定參數 其中key爲參數名 value的值 有1 或2 其中1 是必須添加 2爲選填 option 爲的值路由參數中說起的值(不明白請查閱文檔) pattern 爲次路徑下的參數限制
另外請注意,若是在註冊路由時,指定的方式傳類型
會有一個優化(在我理解)
會將路由直接對應一個true,而其對應的參數在$rules中的*中存儲
dom
protected static function setRule($rule, $route, $type \= '\*', $option \= \[\], $pattern \= \[\], $group \= '') { ~~~~ if (is\_array($rule)) { // 是不是批量註冊 $name \= $rule\[0\]; $rule \= $rule\[1\]; } elseif (is\_string($route)) { $name \= $route; } if (!isset($option\['complete\_match'\])) { // 註冊規則中是否有徹底匹配 if (Config::get('route\_complete\_match')) { //配置文件中是否有徹底匹配 $option\['complete\_match'\] = true; } elseif ('$' \== substr($rule, \-1, 1)) { // 註冊路由表達式中是否包含結束副 // 是否完整匹配 $option\['complete\_match'\] = true; } } elseif (empty($option\['complete\_match'\]) && '$' \== substr($rule, \-1, 1)) { // 是否完整匹配 $option\['complete\_match'\] = true; } if ('$' \== substr($rule, \-1, 1)) { //去掉表達式中的結束符 $rule \= substr($rule, 0, \-1); } if ('/' != $rule || $group) { $rule \= trim($rule, '/'); } $vars \= self::parseVar($rule); // 提取表達式中的參數設定 if (isset($name)) { $key \= $group ? $group . ($rule ? '/' . $rule : '') : $rule; $suffix \= isset($option\['ext'\]) ? $option\['ext'\] : null; self::name($name, \[$key, $vars, self::$domain, $suffix\]); /*註冊$rules的name成員, key 爲對應的映射地址 0 ==》 路由表達式 1 ==》 參數列表 2 ==》 域名 4 ==》 後綴 同時一個映射地址能夠存儲多個 */ } if (isset($option\['modular'\])) { $route \= $option\['modular'\] . '/' . $route; } if ($group) { //是否設置分組 if ('\*' != $type) { //記錄是哪一種方式 $option\['method'\] = $type; } //是否設置包含域名 if (self::$domain) { self::$rules\['domain'\]\[self::$domain\]\['\*'\]\[$group\]\['rule'\]\[\] = \['rule' \=> $rule, 'route' \=> $route, 'var' \=> $vars, 'option' \=> $option, 'pattern' \=> $pattern\]; } else { self::$rules\['\*'\]\[$group\]\['rule'\]\[\] = \['rule' \=> $rule, 'route' \=> $route, 'var' \=> $vars, 'option' \=> $option, 'pattern' \=> $pattern\]; } } else { if ('\*' != $type && isset(self::$rules\['\*'\]\[$rule\])) { unset(self::$rules\['\*'\]\[$rule\]); } if (self::$domain) { self::$rules\['domain'\]\[self::$domain\]\[$type\]\[$rule\] = \['rule' \=> $rule, 'route' \=> $route, 'var' \=> $vars, 'option' \=> $option, 'pattern' \=> $pattern\]; } else { self::$rules\[$type\]\[$rule\] = \['rule' \=> $rule, 'route' \=> $route, 'var' \=> $vars, 'option' \=> $option, 'pattern' \=> $pattern\]; //註冊對應值 } if ('\*' \== $type) { // 註冊路由快捷方式 foreach (\['get', 'post', 'put', 'delete', 'patch', 'head', 'options'\] as $method) { if (self::$domain && !isset(self::$rules\['domain'\]\[self::$domain\]\[$method\]\[$rule\])) { self::$rules\['domain'\]\[self::$domain\]\[$method\]\[$rule\] = true; } elseif (!self::$domain && !isset(self::$rules\[$method\]\[$rule\])) { self::$rules\[$method\]\[$rule\] = true; } } } }}
到這裏爲止,咱們經過兩種方式進行了路由到註冊
1 引用的Route.php 中經過使用Route::rule,Route::get等方法註冊的
3 經過Route::import導入配置文件中的批量配置
路由檢測主要集中在check方法內 主要過程是
靜態檢測
路由規則檢測
也就是一下6部分
1 檢查路由緩存
若開啓 route_check_cache 時,則在第一次緩存後將匹配成功的路由參數
存儲在緩存中
直接調用Route::parseRule進行解析
其實在check的最後檢查過5項後最後第六步也會調用parserule進行路由轉換,這裏就一併分析了
簡單分析下parserule關鍵的地方
(1)閉包註冊
判斷是否爲閉包類 若是是result返回閉包對象
(2)若是路由是到重定向地址
tp5路由規則中 首字符爲/ 或包含http://既爲重定向地址
若是是則返回result 包含當前的路由地址,若是路由沒有設置status參數則默認爲301
(3)路由到方法
tp5 的路由到方法的格式爲
路由地址必須從跟命名空間開始,因此開頭必須爲'\'
這裏處理將$route 中的@替換回來
提取@後的method
最後返回result
(4)路由到控制器操做
當首字符爲@的時候則定義到控制器方法,
這裏除了解析字符串返回$result對象意外
會調用Request->Action執行對應的方法
(5)路由到普通的 既模塊/控制器/操做
最後在解析完成後 Route::parseRule 會返回一個result數組,爲路由的調用提供依據。
2 檢測路由別名
別名的設置以下
咱們能夠看到 若是別名對應存儲的不是一個字符串地址,而是參數數組,則會依次檢查參數的是否符合
後面依次是參數有效性檢查 ; 檢測別名對應地址,匹配其路由到類,路由到控制器,路由到模塊/控制器,的需求
最後若是匹配成功,返回result數組,若是匹配失敗則返回false,執行其他步驟
3 檢測域名部署
這裏也將域名的註冊 和域名的檢查同時介紹
域名註冊:
域名能夠綁定的類型有三種
下圖爲註冊函數
能夠看見domain函數會直接進行註冊,將路由地址,路由參數,參數規則
這裏我以爲閉包函數的規則有點雞肋
1 執行時機
竟然是在執行註冊的時候就進行調用,我不明白這樣的調用有什麼意義
2 另外只執行了調用卻沒有對閉包的任何參數進行註冊。
閉包存在的意義就只有分別對各自域名進行註冊
但是因爲這個閉包註冊是定義在配置文件中的,那樣就是說不論我是訪問哪個子域名,我仍是要加載全部的閉包註冊。因爲這些緣由因此感受仍是比較雞肋的。
而動態註冊規則
最終會執行
將參數的規則除了在name中
另外註冊一份在domain中,結構以下圖
域名檢測
對域名進行解析
得到當前路由規則中對應的值
若是對應的規則包含'['bind']' 說明不是動態註冊規則,則在解析後,將路由規則註冊進$bind私有成員變量
若是不包含說明是動態註冊規則
則替換目前的規則
4 檢測URL綁定
這裏就是處理上一步中域名綁定的規則,將其處理爲result返回跳轉
5 靜態路由規則檢測
靜態路由是指,訪問連接和註冊連接一致
6 路由規則檢測
路由規則檢測最後部分是調用的是Route::parseRule,這一部分在1中已經分析過,在這裏就不重複了,這裏只分析Route::checkRoute層面上作了什麼
1 遍歷當前訪問方式的rules
當前訪問方式的規則數組rules,遍歷當前訪問方式的路由規則
若是是分組路由則遞歸調用自身,並將分組參數傳入
也就是說咱們註冊時填充的路由分組,到這裏會統一將分組名稱填寫徹底後進行路由檢測
執行前置的參數檢查,最後在C:wamp64wwwthinkphplibrarythinkRoute::match中執行路由的參數,和當前url 是否符合路由的檢測
檢測$m2 爲當前路由規則
$m1 爲當前url
從註冊中能夠看出路由規則 和咱們當前的訪問url 除開參數意外,應該是一一對應的,代碼中爲
1 檢測是否爲變量2 檢測是否爲可選參數,若是是,去掉[]並將表明是否爲可選參數的變量置爲響應的值3 執行爲參數的邏輯4 當不爲參數時,檢測當前url是否和規則相等