前言
今天是2019年12月30號了,哈哈,舊的一年即將過去,新的一年即將來臨。老司機繼續帶你在裝逼的道路上越走越遠,沒錯,我們要做一個硬核的裝逼俠。
準備工作
由於我現在講解的每一部分內容都不是獨立存在的,與前面講解的內容有關聯,所以我希望你至少閱讀過一下2篇博文,這是必要的:
程式碼講解
我們直接進入到Illuminate\Routing\Router
類的:
直接呼叫dispatchToRoute
方法,很簡單:
呼叫runRoute
方法:
繼續呼叫runRouteWithinStack
方法:
上面貼出的程式碼,我已經在Laravel 之路由分發和Laravel 之路由匹配講解過多次了,不熟悉的可以再次回去看一遍,不在多講,我們直接看$route->run()
,即呼叫Illuminate\Routing\Route
類的run
方法:
這裡我們只關心路由中指定控制器的情況,假如說我在web.php
檔案中,我定義了一個路由:
Route::get('/debug/args/{id}/{name}', 'Admin\DebugController@args');
請大家記住這個路由,後面我們會用到,注意了我在路由中指定了2個引數,分別是id
和name
,控制器是DebugController
,動作是args
。關於DebugController
的定義如下:
class DebugController extends Controller
{
public function args(DebugFormRequest $request, $name, $id)
{
dd($name, $id);
}
}
這裡先不多說,我們回到Illuminate\Routing\Route
類的run
方法,繼續呼叫runController
方法:
這裡的controllerDispatcher
方法返回的是Illuminate\Routing\ControllerDispatcher
類的物件,關於這個我也在之前的2篇博文中說過了,我們進入到它的dispatch
方法:
上面我標出來的就是今天分析的重點,$route->parametersWithoutNulls()
返回的當前路由的引數,比如對於前面定義的路由,當我訪問它時,http://localhost/debug/args/2/DennisRitche
,此時我列印它的結果:
這個很好理解,接下來,我們繼續分析resolveClassMethodDependencies
方法,這個方法屬於Illuminate\Routing\RouteDependencyResolverTrait
這個trait的,因為當前的ControllerDispatcher使用到了這個trait,我們來看它:
我們進入到resolveClassMethodDependencies
方法中:
這個方法用於解析當前控制器方法的依賴性引數,1開始檢測控制器是否存在這個方法,2才真正開始解析引數依賴,我們進入到resolveMethodDependencies
方法中,這裡有必要提醒一下,如果你對PHP的反射不清楚的話,請檢視我之前寫的這篇詳解 PHP 反射的基本使用:
這個方法本身並不複雜,但是我不得不吐槽Laravel自身設計上的一點坑,為啥我怎麼說,當我訪問/debug/args/2/DennisRitche
這個路由的時候,控制器的列印結果如下:
但是,關鍵的問題是,這不是我想要的,因為我的控制器方法是這麼定義的:
所以列印的結果並不是我想要的,至於原因就在這個resolveMethodDependencies
方法中,在這個方法中,迴圈遍歷$reflector->getParameters()
返回的引數,每一個引數都是ReflectionParameter
類的物件,transformDependency
用於檢測當前的引數是不是一個類物件,如果是的話,就返回一個這個類的物件:
getClass
方法獲取引數的類名,接下來的程式碼,就是根據它的類名建立物件,當然了首先還是判斷它是否存在預設值,返回到resolveMethodDependencies
方法中,我們看,這段程式碼可能有些迷惑,我仔細給大家分析:
首先is_null
判斷transformDependency
返回的值是不是為空,如果不為空的話,$instanceCount
值加1,這個值表示解析出的例項數,$key
表示當前的引數在引數中的位置,以DebugController
的args
方法為例:
因為第一個引數$request
是DebugFormRequest
類的物件,所以此時$key
為0,因此$instanceCount
的值為1。說到這裡,有必要再說下spliceIntoParameters
方法,這個方法很簡單,但是在當前的分析中,很有必要解釋哈:
方法array_splice
會在$parameters
的$offset
位置插入一個元素,插入的值是$value
。
我們看此時插入$request
的時候,檢視結果:
返回到resolveMethodDependencies
接續迭代下一個引數,此時的引數為$name
,關鍵的問題來了,程式碼會進入到elseif語句中,在分析它的程式碼前,我們來看哈,$values
的值為:
控制檯列印:
好了,我們進入到elseif語句中,! isset($values[$key - $instanceCount])
這個判斷語句是啥意思呢?前面我們說了$instanceCount
表示解析出的例項數,而$key
表示當前引數在控制器方法所有引數中的位置,這個地方表明Laravel會在所有的路由引數中依次取值,而不是按引數名進行取值,這就是我之前給大家說的Laravel坑,這就是為啥當我列印控制器引數的時候,$name
值為2,$id
引數為"DennisRitche"。如果! isset($values[$key - $instanceCount])
的返回值為false,表示路由中無法解析出這個引數值,$parameter->isDefaultValueAvailable()
檢查引數是否存在預設值,所以總結下elseif的判斷條件意思就是,當路由中不存在這個引數的時候,檢查這個引數是否存在預設值,如果存在的話,$parameter->getDefaultValue()
會獲取預設值並把它儲存到$parameters
的相對應位置。
好了,resolveMethodDependencies
這裡關鍵的地方已經給大家解釋了。
解析完所有的引數之後,我們回到Illuminate\Routing\ControllerDispatcher
的dispatch
方法中:
1檢測當前的控制器是否存在callAction
方法,如果存在就呼叫它,如果不存在就直接呼叫對應的控制器方法。
總結
今天的這篇博文,我簡要的給大家講述了Laravel是如何解析控制器方法的引數的,這是下一篇給大家講解Laravel驗證器的基礎條件,希望大家能理解,歡迎加入qq群: