3.3.1 - Laravel - 5.6 - Route - 路由对象Route的创建过程

还是使用上节的这个例子:

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 协议》,转载必须注明作者和本文链接
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!