ThinkPHP路由源碼解析(一)(thinkphp 源碼)
路由是項(xiàng)目開發(fā)中比較重要的一個(gè)環(huán)節(jié),每個(gè)項(xiàng)目都會使用路由進(jìn)行管理接口,接下來本文會從源碼方面帶大家一起學(xué)習(xí)路由。
框架路由解析
前言
一、路由初識化簡單分析
二、通過定義路由再談門面
三、路由定義rule方法中的$this->group到底執(zhí)行了什么
四、路由規(guī)則預(yù)處理
五、解析生成路由標(biāo)識的快捷訪問
六、總結(jié)
前言
使用框架寫過項(xiàng)目的肯定都使用過路由,使用路由來進(jìn)行接口的管理,那么為什么要使用路由呢!
使用路由會保護(hù)項(xiàng)目的真實(shí)請求路徑。
使請求地址更加規(guī)范和簡潔,在開發(fā)過程中方法名有時(shí)候會很長,就可以直接使用路由進(jìn)行簡潔處理。
可以統(tǒng)一對請求請求進(jìn)行攔截并且進(jìn)行權(quán)限檢查的操作。
并且在5.1版本支持了注解路由,方便在開發(fā)的過程中進(jìn)行調(diào)試。
方便直接對請求進(jìn)行緩存,并且還支持了路由中間件。
接下來咔咔會對路由的方方面面進(jìn)行全面的解析,并且會給大家?guī)夏X圖方便大家最直觀的預(yù)覽。
一、路由初識化簡單分析
在框架執(zhí)行流程那一篇文章中,都知道路由初始化是在初始化應(yīng)用那個(gè)過程中執(zhí)行的。
然后進(jìn)入到routeInit這個(gè)方法,進(jìn)行代碼解析。
來到這個(gè)方法先看代碼注釋,注釋為導(dǎo)入路由定義規(guī)則。
這段代碼的全部我給復(fù)制出來了,接下來就是對這段代碼進(jìn)行解析。
/** * 路由初始化 導(dǎo)入路由定義規(guī)則 * @access public * @return void */ public function routeInit() { // 路由檢測 // scandir:返回置頂目錄的文件數(shù)組形式 $files = scandir($this->routePath); foreach ($files as $file) { if (strpos($file, '.php')) { $filename = $this->routePath . $file; // 導(dǎo)入路由配置 $rules = include $filename; if (is_array($rules)) { $this->route->import($rules); } } } if ($this->route->config('route_annotation')) { // 自動生成路由定義 if ($this->appDebug) { $suffix = $this->route->config('controller_suffix') || $this->route->config('class_suffix'); $this->build->buildRoute($suffix); } $filename = $this->runtimePath . 'build_route.php'; if (is_file($filename)) { include $filename; } } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
首先會獲取route目錄下的文件,函數(shù)scandir會返回指定目錄的文件并且用數(shù)組形式返回。
這里返回結(jié)果有三個(gè),第一個(gè)為當(dāng)前目錄,第二個(gè)為父級目錄。這倆個(gè)數(shù)據(jù)不用過多追究。
接著就會將route目錄下存在php結(jié)尾的文件給導(dǎo)入進(jìn)來,也就是route.php文件。
當(dāng)把路由文件導(dǎo)入進(jìn)來之后,進(jìn)行了一次判斷然后進(jìn)行導(dǎo)入規(guī)則。
但是在路由文件可以看到是沒有返回任何數(shù)據(jù)的。
路由文件的return演示
那么這里的return是干嘛的呢!在5.1版本之前是沒有這一操作的,但是在5.1是存在的,接下來咔咔給大家演示一下這個(gè)使用方法。
首先在路由文件的return中配置一條數(shù)據(jù)。
然后在index文件中創(chuàng)建一個(gè)新的方法vpn
此時(shí)可以直接訪問路由vpn即可。
當(dāng)route文件的return存在的數(shù)據(jù)的時(shí)候,就會執(zhí)行到$this->route->import($rules);這一步,本節(jié)暫時(shí)不對這里做出探討,會在后文中大家詳細(xì)說明。
下半截源碼閱讀
第一行代碼知道這里的數(shù)據(jù)是從哪里來的嗎?
在應(yīng)用初始化的initialize方法中,執(zhí)行了init方法,在init方法中執(zhí)行了容器中實(shí)例進(jìn)行配置更新,在哪里進(jìn)行設(shè)置的。如下圖
也就說在這里給路由添加的配置,并且可以看到路由的配置文件是來源于app配置文件。
想要知道這個(gè)配置是干什么的就需要從根源去追溯,所以就需要到app的配置文件中去尋找。
可以看到這個(gè)參數(shù)指的是注解路由的開關(guān)。
在緊接著就是根據(jù)注釋自動生成路由規(guī)則,這一塊的代碼暫時(shí)就說到這里,下文會對這些代碼進(jìn)行詳細(xì)的說明。
二、通過定義路由再談門面
這里只是簡單提一下,關(guān)于路由的一些使用方法之類的就可以直接去文檔去看了,咔咔也不用做搬運(yùn)工在搬過來。
先在路由文件定義一個(gè)kaka的路由。
但是這個(gè)get方法是怎么執(zhí)行的呢!代碼追蹤也直接追不過去,那么這個(gè)時(shí)候應(yīng)該怎么做。
第一種辦法,在框架中所有的基礎(chǔ)類都是注冊了門面模式的,所以在路由里邊引入facade就可以進(jìn)行代碼追蹤了。
但是既然都把門面模式的源碼都看了怎么還會去使用第一種辦法,肯定要把其中的一些小細(xì)節(jié)給扒出來。
在base文件中已經(jīng)注冊了門面的別名了,所以說在引入文件時(shí)可以使用use Config,也可以使用use think\facade\Config;
這個(gè)時(shí)候就會來到thinkphp/library/think/facade/Config.php這個(gè)文件。
可以看到是繼承了門面類的。
來到Facade這個(gè)類中,拉到文件最后可以看到一個(gè)方法__callStatic,這個(gè)方法是當(dāng)調(diào)用不存在的靜態(tài)的方法時(shí)就會執(zhí)行這個(gè)方法。
最后就會去執(zhí)行創(chuàng)建門面類的方法,最終會通過容器返回返回對應(yīng)的實(shí)例,比如使用的是Config,最終就會返回Config的這類的實(shí)例。
最后就會回到開始,使用call_user_func_array進(jìn)行執(zhí)行對應(yīng)實(shí)例的方法。
static::createFacade()會返回Config的對應(yīng)實(shí)例。
這一塊內(nèi)容就說到這里了,雖然說之前在門面中也提到了但是在這里在回顧一下也是很有必要的,為故而知新嘛!
三、路由定義rule方法中的$this->group到底執(zhí)行了什么
通過定義路由再談門面這一節(jié)中,就可以知道下圖中的rule方法會執(zhí)行那個(gè)類的那個(gè)方法。
根據(jù)之前咔咔給大家介紹的方法就可以知道最終會執(zhí)行到thinkphp/library/think/Route.php這個(gè)文件中的rule方法。
這里就會出現(xiàn)一個(gè)問題就是這個(gè)group是什么,是怎么執(zhí)行的,其實(shí)在上邊的圖已經(jīng)描述的很明白了。
咔咔在用文字給大家在說一遍,可以對照著進(jìn)行查看源碼。
先看一下這個(gè)方法熟悉不熟悉。
沒錯(cuò)這個(gè)方法在容器那一節(jié)中進(jìn)行了特別的講解,如果不明白的去前言查看之前的文章進(jìn)行查看。
那么這個(gè)方法在哪里執(zhí)行的,咔咔就直接說了,就不說的那么細(xì)了。
當(dāng)使用Route::rule();時(shí)就會執(zhí)行到門面的__callStatic方法。
這個(gè)方法已經(jīng)說了好多次了,當(dāng)執(zhí)行的靜態(tài)方法不存在時(shí)就會執(zhí)行。
然后就會去執(zhí)行createFacade這個(gè)方法。
在這個(gè)方法需要特別注意最后一行,也就是圈出來的哪一行代碼。
這里需要注意一下static::class,它返回的就是Route類,至于是怎么返回的之前也說過,如果不會的可以評論區(qū)見哈!
所以make方法的第一個(gè)參數(shù)就是Route。
接著就會進(jìn)入到容器類的make方法,在這個(gè)方法中,注意咔咔圈的倆處。
在容器類中一定要注意的四個(gè)屬性,咔咔給大家畫了出來。
第一次進(jìn)來會存入到容器標(biāo)識名中。
并且第二次執(zhí)行make方法時(shí)傳的是標(biāo)識名的值。
直到這里就會執(zhí)行到咔咔圈的第二處,執(zhí)行invokeClass這個(gè)方法。
來到invokeClass方法中。
在這個(gè)方法中主要使用的是反射的知識。
首先會把傳過來的think\Route,進(jìn)行反射類的實(shí)例化。
然后會去判斷think\Route中是否存在__make方法。
最后就會去執(zhí)行Route類的__make方法。
并且進(jìn)行依賴注入App類和Config類。
同時(shí)進(jìn)行了實(shí)例化了本類就是代碼$route = new static($app, $config);
那么就會去執(zhí)行構(gòu)造函數(shù)。
接著來到構(gòu)造函數(shù)的方法中查看。
這里一定要注意第二步的注釋,這個(gè)已經(jīng)說了很多次了。
如果有疑問就是為什么使用了ArrayAccess。
咔咔就給大家捋一下,首先App類是繼承的Container類。
Container類又繼承著ArrayAccess類,所以就可以使用像之前的說,像數(shù)組一樣訪問對象。
注入的App類
此處使用了ArrayAccess像數(shù)組一樣訪問對象,但是$app中不存在request屬性,所以就會去執(zhí)行容器類中的__get魔術(shù)方法,在__get方法中調(diào)用的是容器中的make方法,第一個(gè)參數(shù)為request,最終會返回request的實(shí)例。
注入的config類
此屬性返回域名,
初始化默認(rèn)域名
接著就會來到方法setDefaultDomain。
這里需要注意一下咔咔圈的地方,在這一節(jié)的開頭就使用的這個(gè)屬性來執(zhí)行的rule中的addRule方法。
這個(gè)group就是think\Route\Domain這個(gè)類。
又因?yàn)閠hink\Route\Domain繼承著think\Route\Group
所以會執(zhí)行到think\Route\Group到這個(gè)類里邊。
這下所有的流程就都已經(jīng)理順了。
通過上面的這一頓分析和刨銑相信大家對這一塊的內(nèi)容就十分了解了。
四、路由規(guī)則預(yù)處理
接下來的內(nèi)容就是對路由規(guī)則的處理方式進(jìn)行解析。
這幾個(gè)參數(shù)就對前倆個(gè)做一個(gè)簡單的打印,然后看一下這倆個(gè)參數(shù)分別都是什么。
參數(shù)rule
參數(shù)$route
可以看到這倆個(gè)參數(shù)幾個(gè)就是 路由的前半部分,一個(gè)是路由的后半部分。
從而就可以得知代碼會執(zhí)行到下圖第一處圈起來的地方。
此時(shí)需要注意一點(diǎn)就是在上圖中圈起來的第二處地方,這個(gè)地方會在什么時(shí)候執(zhí)行呢!
就是當(dāng)下圖路由這樣設(shè)置的時(shí)候才會執(zhí)行那段代碼。
這里的路由設(shè)置只是為了做演示,在實(shí)際工作中不能這樣設(shè)置路由啊!第二個(gè)路由地址會把第一個(gè)路由地址給覆蓋的。
接著代碼就會執(zhí)行到創(chuàng)建路由規(guī)則實(shí)例,也就是下圖圈出來的地方。
關(guān)于創(chuàng)建路由規(guī)則實(shí)例的幾個(gè)參數(shù)需要進(jìn)行簡單的介紹一下
第一個(gè)參數(shù):$this->router
第二個(gè)參數(shù):$this,就是指的think\route\Domain。
接下來的幾個(gè)地址就是路由規(guī)則和地址,就沒有必要看了,主要就是第一個(gè)參數(shù)。
接下來就需要進(jìn)入到創(chuàng)建路由規(guī)則實(shí)例的方法中
代碼就會追蹤到thinkphp/library/think/route/RuleItem.php這個(gè)類中。
在這個(gè)類中做的事情就是設(shè)置規(guī)則,也就是下圖咔咔圈出來的地方。
路由規(guī)則預(yù)處理setRule方法解讀。
上圖中最后執(zhí)行的流程就會來到setRule這個(gè)方法。
這段代碼沒有我們眼看這那么容器閱讀,接下來就跟著咔咔一步一步的去閱讀這塊代碼。
1.首先代碼就會執(zhí)行這部分
從一開始就進(jìn)行了一個(gè)簡單的判斷$,那么這個(gè)是從哪里過來的呢!
在文件thinkphp/library/think/route/RuleGroup.php這里就對$進(jìn)行了處理。
所以說在什么情況下會執(zhí)行預(yù)處理的第一部分呢!
我們來看一下簡單的案例。
然后在路由預(yù)處理中進(jìn)行打印,就會發(fā)現(xiàn)打印結(jié)果后邊會多一個(gè)$符號。
substr介紹
這里對substr進(jìn)行簡單的說明一下,碰到每一個(gè)知識點(diǎn)都簡單的回顧一下。
這個(gè)方法用來字符串截取。
參數(shù)一:需要截取的字符串
參數(shù)二:從什么位置開始截取,正數(shù)從左往右,負(fù)數(shù)結(jié)尾開始,0 - 在字符串中的第一個(gè)字符處開始
參數(shù)三:長度,正數(shù)從參數(shù)二進(jìn)行返回,負(fù)數(shù)從末端進(jìn)行返回。
所以說最終這塊代碼的執(zhí)行會把$這個(gè)符號取消掉,只留為/,打印結(jié)果如下圖。
接著就是對路由預(yù)處理的下半部分進(jìn)行解析。
先進(jìn)行一次移除左側(cè)所有的斜杠的處理。
在接下來的$this->parent大家有有沒有一點(diǎn)點(diǎn)疑問,這里是指的哪里。
參數(shù)是從這里開始進(jìn)行傳遞的。
經(jīng)過上文的分析,我們知道$this->group就是實(shí)例化的think\route\Domain。
緊接著會跳轉(zhuǎn)到下圖,這里的$this就是就是think\route\Domain,要不就沒辦法解釋的。
這塊有問題的可以去看第三節(jié)文章。
所以說這里的$parent就是think\route\Domain這個(gè)類。
所以說這塊代碼就迎刃而解了。
所以說第三節(jié)哪里沒看明白的一定要自己拉著代碼好好的看,否則越往后越不明白。
這塊代碼具體做了什么事情后邊在深入詳解,這里就主要針對$this->parent它是怎么執(zhí)行的就可以了。
在接著就是這塊代碼,這塊就是對理由參數(shù)做了倆中處理。
把帶有:符號的參數(shù)轉(zhuǎn)化。
例如::name 轉(zhuǎn)化為
放在代碼中就是$this->rule =
五、解析生成路由標(biāo)識的快捷訪問
從路由處理規(guī)則之后還有一步操作就是生成路由標(biāo)識的快捷訪問。
路由規(guī)則中的變量
但是在前邊我們需要對路由規(guī)則中的變量做一點(diǎn)簡單的說明。
下圖圈出來的地方就是將要解析的內(nèi)容。
來到parseVar這個(gè)方法,這個(gè)方法是在thinkphp/library/think/route/Rule.php這個(gè)類里,因?yàn)楸绢惱^承的rule類。
那么接下來就是要對這一整塊的內(nèi)容作出詳細(xì)的說明了。
首先要先明白preg_match_all的三個(gè)參數(shù)是什么。
參數(shù)一:需要搜索的字符串
參數(shù)二:輸入的字符串
參數(shù)三:多維數(shù)組,作為輸出參數(shù)輸出所有匹配結(jié)果, 數(shù)組排序通過flags指定。
所以說最終的匹配結(jié)果會存放在變量$matches中。
那么先來打印一下$matches這個(gè)變量。
這里需要先說明一個(gè)關(guān)于PHP版本的問題。
此次使用的路由案例為下圖
在打印這個(gè)值的時(shí)候,咔咔遇到了一點(diǎn)點(diǎn)問題,在PHP7.3.4中使用dump是無法顯示數(shù)據(jù)的,但是在dubug調(diào)試下會顯示。
在PHP版本7.2.9下dump是顯示的。
關(guān)于這個(gè)問題,咔咔就先繞過了,如果有大佬知道情況的可以給咔咔簡單的那么嘮一嘮,這里咔咔就使用PHP7.2.9來進(jìn)行講解了。
后期咔咔在針對這個(gè)問題進(jìn)行簡單的說明一下,遇到問題就需要去解決嘛!
那么你知道在上圖的打印結(jié)果中
在到這里說一次哈!
就是下圖為路由規(guī)則預(yù)處理的地方進(jìn)行正則處理的,看到這里關(guān)于上面的那個(gè)問題,咔咔初步認(rèn)為在不同版本中要木preg_replace這個(gè)方法進(jìn)行了改動,要木就是關(guān)于正則方面進(jìn)行了改動。
所以說把這塊搞定后,其余的代碼就是毛毛雨啦!
這里就是在根據(jù)?第一次出現(xiàn)的問題進(jìn)行判斷。
在上文中咔咔給出的路由案例中相信大家也看到了另外一個(gè)跟案例很相似的路由。
這倆組路由參數(shù)分別為參數(shù)必有,和可選參數(shù)。
在打印截圖中咔咔只是說明了一種情況,另一種情況就需要大家去進(jìn)行測試了。
最終會把結(jié)果返回給$var,接下來我們就光打印一下倆種情況的返回值就可以了。
情況一:Route::get(‘hello/[:name]’, ‘index/index/hello’);
情況二:Route::get(‘hello/:name’, ‘index/index/hello’);
從而可以得出結(jié)論就是,當(dāng)參數(shù)可選時(shí)為2,必選時(shí)為1。
截止到這里關(guān)于路由規(guī)則中的變量一些處理就簡單說到這里。
這塊的代碼沒什么難的,只要會preg_match_all和strpos、substr就可以了。
解析生成路由標(biāo)識的快捷訪問
接下來就繼續(xù)來看這塊內(nèi)容。
咔咔下圖圈出來的就暫時(shí)先不用看,在下文中會單獨(dú)提出來來說這一塊。
那么接著就需要看一個(gè)$value這個(gè)值是什么了。
下圖就是打印結(jié)果,可以看到都包含了路由規(guī)則,參數(shù)是否可選,請求域名,請求方式。
最后又來到我們最熟悉的容器模塊了Container::get('rule_name')->set($name, $value, $first);,也就是這行代碼。
參數(shù)就是rule_name。
然后執(zhí)行make方法,因?yàn)樵谌萜黝惱镞呉呀?jīng)把rule_name進(jìn)行了綁定。
最終通過反射將object(think\route\RuleName)返回回來,并且存放在容器里邊。
此時(shí)就相當(dāng)于是$this->instances['think\route\RuleName'] = object(think\route\RuleName);
最后一步就是設(shè)置路由規(guī)則。
截止到這里就把路由標(biāo)識的快捷訪問就解析完了。
由于路由這塊的內(nèi)容還有將近一半的內(nèi)容,一文寫完篇幅有點(diǎn)太長
本文就到這里,下一文接著路由在來談其它內(nèi)容。
六、總結(jié)
在這一文中主要針對路由的執(zhí)行流程、通過定義路由的方式在一次對門面進(jìn)行深究。
同時(shí)對rule方法中的group進(jìn)行了特別詳細(xì)的講解,這塊的內(nèi)容必須要好好的研究一下,類與類之間的繼承一定要注意。
在就是關(guān)于路由規(guī)則的預(yù)處理,就是對于路由的前半部分進(jìn)行處理。
主要就是以下的三句話。
把帶有:符號的參數(shù)轉(zhuǎn)化。
例如::name 轉(zhuǎn)化為 。
放在代碼中就是$this->rule =
緊接著就是對生生路由標(biāo)識的快捷訪問進(jìn)行了源碼的閱讀,在這一節(jié)最重要的就是對變量的處理。
判斷參數(shù)是可選的還是不可選的,可選為2,不可選為1,作為標(biāo)識。
以下是咔咔畫出來的腦圖。
這個(gè)腦圖還沒有寫完整,后邊還有內(nèi)容,在下文會進(jìn)行詳細(xì)的補(bǔ)充。
最后就是在給大家把這個(gè)圖給大家放出來,這個(gè)圖是最詳細(xì)的一個(gè)執(zhí)行流程。
關(guān)于類與類之間的執(zhí)行關(guān)系,都在圖中有詳細(xì)的描述。
堅(jiān)持學(xué)習(xí)、堅(jiān)持寫博、堅(jiān)持分享是咔咔從業(yè)以來一直所秉持的信念。希望在偌大互聯(lián)網(wǎng)中咔咔的文章能帶給你一絲絲幫助。我是咔咔,下期見。
NAT ThinkPHP
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。