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的数字。
我们可以在初始化某个服务中这样设置:调用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 协议》,转载必须注明作者和本文链接