還是使用上節的這個例子:
Route::get('connect', 'AccountController@connect')->name('connect');
經過一系列的呼叫,這個方法最終會走到這裡。呼叫createRoute建立路由例項:
首先強調下當前的例子中對應的createRoute引數分別是:
method : [get, head],
url : 'connect'
action : 'AccountController@connect'
然後看下createRoute原始碼 如下:
protected function createRoute($methods, $uri, $action)
{
if ($this->actionReferencesController($action)) {
$action = $this->convertToControllerAction($action);
}
$route = $this->newRoute(
$methods, $this->prefix($uri), $action
);
if ($this->hasGroupStack()) {
$this->mergeGroupAttributesIntoRoute($route);
}
$this->addWhereClausesToRoute($route);
return $route;
}
1.首先,createRoute呼叫了actionReferencesController方法:
protected function actionReferencesController($action)
{
if (! $action instanceof Closure) {
return is_string($action) || (isset($action['uses']) && is_string($action['uses']));
}
return false;
}
這個方法很簡單就是判斷當前的這個action是不是閉包。如果不是閉包:
a.是不是string,如果是string就直接返回
b.如果是陣列,要求陣列有uses欄位,並且這個欄位是string型別。
讀到這裡我們可以知道
Route::get
這些方法的 第二個引數action 有幾種形式的寫法:
//字串Route::get('user', 'ThreadsController@show');
//閉包Route::get('user', function(){ ... });
//不常用的陣列形式Route::get('user', array('before' => 'old', 'uses' => 'UserController@showProfile'));
2.接著呼叫了convertToControllerAction方法對非閉包的action進行資料格式的處理。就是把資料轉換成我們需要的樣子:
大體上主要做了這麼幾件事:
1.action是string的話,把它轉變成陣列
2.如果有父級的namespace引數,新增這個namespace到action上,合併成完整路徑 //關於父級別namespace和GroupStack 參考3.2章節的內容。
3.最後把 uses欄位資料 賦值給controller
欄位 //暫時還不知道這個複製有什麼用。
程式碼如下:
protected function convertToControllerAction($action)
{
if (is_string($action)) {
$action = ['uses' => $action];
}
if ($this->hasGroupStack()) {
$action['uses'] = $this->prependGroupNamespace($action['uses']);
}
$action['controller'] = $action['uses'];
return $action;
}
分析如下:
2.1 如果這個action是string。直接組裝成鍵值為uses的一個陣列:就是把 string的action變成陣列形式的action。例子:
$action = [
'uses' => ’string型別的action‘
];
2.2 hasGroupStack判斷當前是否存在一些全域性規則: 3.2.1章節介紹過 就是當前路由上級如果有group方法,那就有一些全域性變數的限制,像 namespace,prefix等。
如果存在上級的group的namespace就呼叫prependGroupNamespace方法新增namespace,這裡主要是新增namespace這個限制:
protected function prependGroupNamespace($class)
{
$group = end($this->groupStack);
return isset($group['namespace']) && strpos($class, '\\') !== 0
? $group['namespace'].'\\'.$class : $class;
}
簡單看下prependGroupNamespace:
groupStack陣列中拿出最後一個,然後找到namespace,如果有的話,最後加在action這個資料的前面。
2.3 最後把欄位uses賦值給controller欄位;返回這個陣列。
3. 呼叫newRoute生成Route物件
$route = $this->newRoute(
$methods, $this->prefix($uri), $action
);
3.1首先呼叫prefix方法,給當前的url新增父級別的字首prefix,如果有的話。
protected function prefix($uri)
{
return trim(trim($this->getLastGroupPrefix(), '/').'/'.trim($uri, '/'), '/') ?: '/';
}
簡單說:就是找到group的groupStack陣列最後一個資料把字首prefix新增到url前面,和前面action新增namespace一樣。然後返回。
3.2然後呼叫newRoute方法:
就是new 建立了Route
物件,同時通過setRouter方法傳遞了當前的Router
物件給Route物件,通過setContainer傳遞了laravel的容器Container物件給Route物件。
再次強調 注意區分 Route 和 Router
public function newRoute($methods, $uri, $action)
{
return (new Route($methods, $uri, $action))
->setRouter($this)
->setContainer($this->container);
}
4.在第三步獲得了Route物件後:來到這裡
if ($this->hasGroupStack()) {
$this->mergeGroupAttributesIntoRoute($route);
}
再次判斷是否有父級別的其他規則,(前面的namespace和prefix已經被處理了),剩下一些規則(domain as等)在這裡可以合併後返回。
我們簡單看下mergeGroupAttributesIntoRoute原始碼:
protected function mergeGroupAttributesIntoRoute($route)
{
$route->setAction($this->mergeWithLastGroup(
$route->getAction(),
$prependExistingPrefix = false
));
}
這裡他通過setAction對合並好group引數的action進行儲存。儲存的地方是當前Route物件的action陣列中。這個陣列儲存了所有action必要的資訊引數。
**這個合併流程後面有機會單獨小節在分析。**
5.呼叫addWhereClausesToRoute實現laravel的patterns機制:
$this->addWhereClausesToRoute($route);
addWhereClausesToRoute原始碼如下:
protected function addWhereClausesToRoute($route)
{
$route->where(array_merge(
$this->patterns, $route->getAction()['where'] ?? []
));
return $route;
}
這裡涉及到了laravel的patterns
機制 我把他叫做全域性路由自定義限制。
簡單說就是對路由規則做一個全域性的自定義的限制:比如說我想限制全部的路由裡面的id的值 只能是0-9的數字。
我們可以在初始化某個服務中這樣設定:呼叫Router
的 pattern
方法,Router類
會儲存這個規則到Router
的where變數
中。
public function boot()
{
Route::pattern('id', '[0-9]+');
}
回到原始碼:那這裡的這個 where
方法主要的工作就是
1.合併merge 上面自定義的pattern
規則 和 物件Route下的action陣列的where欄位。組合成一個新的只有where
欄位的陣列
2.然後把這個已經合併了的where
陣列通過where方法新增到 Route
物件的where 陣列
中儲存起來。
強調下 Router類下where變數儲存的是 我們呼叫pattern時候存入的規則。
而Route中的where變數是我們合併了Router下的where和自身路由的where後的值。
看下where方法的程式碼
public function where($name, $expression = null)
{
foreach ($this->parseWhere($name, $expression) as $name => $expression) {
$this->wheres[$name] = $expression;
}
return $this;
}
首先parseWhere對當前的引數進行解析:
protected function parseWhere($name, $expression)
{
return is_array($name) ? $name : [$name => $expression];
}
很簡單,如果name引數是一個陣列形式,就返回。否則把name引數作為key,expression引數作為value,以陣列形式放回。
當前原始碼呼叫的是第一種,就是引數name是一個陣列的形式。
然後 遍歷這個陣列,存入where陣列,就完成了。
儲存的格式是:wheres[$name] = $expression;
到這裡 所有的流程就結束了。
總結:
示例如下:Route::get($url, $action)->name('connect');
建立路由的主要流程
1.判斷action型別。如果是string或者是陣列,轉變成陣列。過程中會合並父級別的namesapce到action裡。如果是閉包,不做任何改變直接儲存。
2.把get方法中的引數資料 用來生成Route物件。建立之前會合並父級別的prefix到url中
3.合併其他父級別的引數,比如domian as等。
4.給已經生成的Route物件新增 patterns。就是全域性的一些自定義的限制。
注意:可以看到全域性的路由限制是在Route物件生成之後新增的。
本作品採用《CC 協議》,轉載必須註明作者和本文連結