3.3.1 - Laravel - 5.6 - Route - 路由物件Route的建立過程

HarveyNorman發表於2021-06-18

還是使用上節的這個例子:

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的數字。

我們可以在初始化某個服務中這樣設定:呼叫Routerpattern方法,Router類會儲存這個規則到Routerwhere變數中。

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 協議》,轉載必須註明作者和本文連結

相關文章