Laravel HTTP-路由加载源码分析

思考并回答以下问题:

前言

作为Laravel极其重要的一部分,route功能贯穿着整个网络请求,是request生命周期的主干。本文主要讲述route服务的注册与启动、路由的属性注册。本篇内容相对简单,更多的是框架添加路由的整体设计流程。

route服务的注册

Laravel在接受到请求后,先进行了服务容器与http核心的初始化,再进行了请求request的构造与分发。

route服务的注册-RoutingServiceProvider发生在服务容器container的初始化上;

route服务的启动与加载-RouteServiceProvider发生在request的分发上。

route服务的注册-RoutingServiceProvider

所有需要Laravel服务的请求都会加载入口文件index.php:

1
2
3
require __DIR__.'/../vendor/autoload.php';

$app = require_once __DIR__.'/../bootstrap/app.php';

第一句我们在之前的博客提过,是实现PSR0、PSR4标准自动加载的功能模块,第二句就是今天说的Container的初始化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<?php

/*
|--------------------------------------------------------------------------
| Create The Application
|--------------------------------------------------------------------------
|
| The first thing we will do is create a new Laravel application instance
| which serves as the "glue" for all the components of Laravel, and is
| the IoC container for the system binding all of the various parts.
|
*/

$app = new Illuminate\Foundation\Application(
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);

/*
|--------------------------------------------------------------------------
| Bind Important Interfaces
|--------------------------------------------------------------------------
|
| Next, we need to bind some important interfaces into the container so
| we will be able to resolve them when needed. The kernels serve the
| incoming requests to this application from both the web and CLI.
|
*/

$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);

$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);

$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);

/*
|--------------------------------------------------------------------------
| Return The Application
|--------------------------------------------------------------------------
|
| This script returns the application instance. The instance is given to
| the calling script so we can separate the building of the instances
| from the actual running of the application and sending responses.
|
*/

return $app;

Application:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
namespace Illuminate\Foundation;

class Application extends Container implements ApplicationContract, CachesConfiguration, CachesRoutes, HttpKernelInterface
{
/**
* Create a new Illuminate application instance.
*
* @param string|null $basePath
* @return void
*/
public function __construct($basePath = null)
{
if ($basePath) {
$this->setBasePath($basePath);
}

$this->registerBaseBindings();
$this->registerBaseServiceProviders();
$this->registerCoreContainerAliases();
}
}

路由服务的注册就在registerBaseServiceProviders()这个函数中:

1
2
3
4
5
6
7
8
9
10
11
/**
* Register all of the base service providers.
*
* @return void
*/
protected function registerBaseServiceProviders()
{
$this->register(new EventServiceProvider($this));
$this->register(new LogServiceProvider($this));
$this->register(new RoutingServiceProvider($this));
}

RoutingServiceProvider:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
namespace Illuminate\Routing;

class RoutingServiceProvider extends ServiceProvider
{
public function register()
{
$this->registerRouter();
...
}

/**
* Register the router instance.
*
* @return void
*/
protected function registerRouter()
{
$this->app->singleton('router', function ($app) {
return new Router($app['events'], $app);
});
}

...
}

可以看到,RoutingServiceProvider做的事情比较简单,就是向服务容易中注册router。

route服务的启动与加载-RouteServiceProvider

Laravel在初始化Application后,就要进行http/Kernel的构造:

1
2
3
4
5
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);

初始化结束后,就会调用handle函数,这个函数用于Laravel各个功能服务的注册启动,还有request的分发:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public function handle($request)
{
try {
$request->enableHttpMethodParameterOverride();
$response = $this->sendRequestThroughRouter($request);
}
return $response;
}

protected function sendRequestThroughRouter($request)
{
$this->app->instance('request', $request);
Facade::clearResolvedInstance('request');
$this->bootstrap();//各种服务的注册与
return (new Pipeline($this->app))//请求的分发
->send($request)
->through($this->app->shouldSkipMiddleware() ? [
] : $this->middleware)
->then($this->dispatchToRouter());
}

路由服务的启动与加载就在其中一个函数中bootstrap,这个函数用于各种服务的注册与启动,比较复杂,我们有机会在以后单独来说。

总之,这个函数会调用RouteServiceProvider这个类的两个函数:注册-register、启动-boot。

由于route的注册工作由之前RoutingServiceProvider完成,所以RouteServiceProvider的register是空的,这里它只负责路由的启动与加载工作,我们主要看boot:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
namespace Illuminate\Foundation\Support\Providers;

class RouteServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
$this->setRootControllerNamespace();

if ($this->routesAreCached()) {
$this->loadCachedRoutes();
} else {
$this->loadRoutes();

$this->app->booted(function () {
$this->app['router']->getRoutes()->refreshNameLookups();
$this->app['router']->getRoutes()->refreshActionLookups();
});
}
}

/**
* Load the cached routes for the application.
*
* @return void
*/
protected function loadCachedRoutes()
{
$this->app->booted(function () {
require $this->app->getCachedRoutesPath();
});
}

/**
* Load the application routes.
*
* @return void
*/
protected function loadRoutes()
{
if (method_exists($this, 'map')) {
$this->app->call([$this, 'map']);
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Application extends Container implements ApplicationContract, CachesConfiguration, CachesRoutes, HttpKernelInterface
{
/**
* Determine if the application routes are cached.
*
* @return bool
*/
public function routesAreCached()
{
return $this['files']->exists($this->getCachedRoutesPath());
}

/**
* Get the path to the routes cache file.
*
* @return string
*/
public function getCachedRoutesPath()
{
return $this->normalizeCachePath('APP_ROUTES_CACHE', 'cache/routes-v7.php');
}
}

从boot中可以看出,Laravel首先去寻找路由的缓存文件,没有缓存文件再去进行加载路由。缓存文件一般在bootstrap/cache/routes.php文件中。

加载路由主要调用map函数,这个函数一般在App\Providers\RouteServiceProvider这个类中,这个类继承上面的Illuminate\Foundation\Support\Providers\RouteServiceProvider:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;

class RouteServiceProvider extends ServiceProvider
{
public function map()
{
$this->mapApiRoutes();
$this->mapWebRoutes();
//
}

protected function mapWebRoutes()
{
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web.php'));
}

protected function mapApiRoutes()
{
Route::prefix('api')
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
}
}

Laravel将路由分为两个大组:api、web。这两个部分的路由分别写在两个文件中:routes/web.php、routes/api.php。

路由的加载

所谓的路由加载,就是将定义路由时添加的属性,例如’name’、’domain’、’scheme’等等保存起来,以待后用。

  • Laravel定义路由的属性的方法很灵活,可以定义在路由群组前,例如:
1
2
3
Route::domain('route.domain.name')->group(function() {
Route::get('foo','controller@method');
})
  • 可以定义在路由群组中,例如:
1
2
3
Route::group('domain' => 'group.domain.name',function() {
Route::get('foo','controller@method');
})
  • 可以定义在method的前面,例如:
1
Route::domain('route.domain.name')->get('foo','controller@method');
  • 可以定义在method中,例如:
1
Route::get('foo', ['domain' => 'route.domain.name','use' => 'controller@method']);
  • 还可以定义在method后,例如:
1
Route::get('{one}', 'use' => 'controller@method')->where('one', '(.+)');

事实上,路由的加载功能主要有三个类负责:

Illuminate\Routing\Router、Illuminate\Routing\Route、Illuminate\Routing\RouteRegistrar。

Router在整个路由功能中都是起着中枢的作用,RouteRegistrar主要负责位于group、method这些函数之前的属性注册,例如上面的第一种和第三种,route主要负责位于group、method这些函数之后的属性注册,例如第五种。

RouteRegistrar路由加载

属性注册

当我们想要在Route后面直接利用domain()、name()等函数来为路由注册属性的时候,我们实际调用的是router的魔术方法__call():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
namespace Illuminate\Routing;

class Router implements BindingRegistrar, RegistrarContract
{
/**
* Dynamically handle calls into the router instance.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
if (static::hasMacro($method)) {
return $this->macroCall($method, $parameters);
}

if ($method === 'middleware') {
return (new RouteRegistrar($this))->attribute($method, is_array($parameters[0]) ? $parameters[0] : $parameters);
}

return (new RouteRegistrar($this))->attribute($method, $parameters[0]);
}
}

在类RouteRegistrar中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class RouteRegistrar
{
/**
* The attributes that can be set through this class.
*
* @var array
*/
protected $allowedAttributes = [
'as', 'domain', 'middleware', 'name', 'namespace', 'prefix', 'where',
];

/**
* Set the value for a given attribute.
*
* @param string $key
* @param mixed $value
* @return $this
*
* @throws \InvalidArgumentException
*/
public function attribute($key, $value)
{
if (! in_array($key, $this->allowedAttributes)) {
throw new InvalidArgumentException("Attribute [{$key}] does not exist.");
}

$this->attributes[Arr::get($this->aliases, $key, $key)] = $value;

return $this;
}
}

添加路由

注册属性之后,创建路由的时候,可以仅仅提供uri,可以提供uri与闭包,可以提供uri与控制器,可以提供uri与数组:

1
2
3
4
5
6
7
8
9
Route::as('Foo')->namespace('Namespace\\Example\\')->get('foo/bar'); // 仅仅uri

Route::as('Foo')->namespace('Namespace\\Example\\')->get('foo/bar', function () {

}); // uri与闭包

Route::as('Foo')->namespace('Namespace\\Example\\')->get('foo/bar', 'controller@method');// uri与控制器

Route::as('Foo')->namespace('Namespace\\Example\\')->get('foo/bar', ['as'=> 'foo','use' =>'controller@method']) ;// uri与数组

利用get、post等方法创建新的路由时,会调用类RouteRegistrar中的魔术方法__call():

Illuminate\Routing\RouteRegistrar.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
class RouteRegistrar
{
/**
* The methods to dynamically pass through to the router.
*
* @var array
*/
protected $passthru = [
'get', 'post', 'put', 'patch', 'delete', 'options', 'any',
];


/**
* Dynamically handle calls into the route registrar.
*
* @param string $method
* @param array $parameters
* @return \Illuminate\Routing\Route|$this
*
* @throws \BadMethodCallException
*/
public function __call($method, $parameters)
{
if (in_array($method, $this->passthru)) {
return $this->registerRoute($method, ...$parameters);
}

if (in_array($method, $this->allowedAttributes)) {
if ($method === 'middleware') {
return $this->attribute($method, is_array($parameters[0]) ? $parameters[0] : $parameters);
}

return $this->attribute($method, $parameters[0]);
}

throw new BadMethodCallException(sprintf(
'Method %s::%s does not exist.', static::class, $method
));
}

/**
* Register a new route with the router.
*
* @param string $method
* @param string $uri
* @param \Closure|array|string|null $action
* @return \Illuminate\Routing\Route
*/
protected function registerRoute($method, $uri, $action = null)
{
if (! is_array($action)) {
$action = array_merge($this->attributes, $action ? ['uses' => $action] : []);
}

return $this->router->{$method}($uri, $this->compileAction($action));
}

/**
* Compile the action into an array including the attributes.
*
* @param \Closure|array|string|null $action
* @return array
*/
protected function compileAction($action)
{
if (is_null($action)) {
return $this->attributes;
}

if (is_string($action) || $action instanceof Closure) {
$action = ['uses' => $action];
}

return array_merge($this->attributes, $action);
}
}

也就是说,RouteRegistrar在这里会为闭包或控制器等所有非数组的action添加use键,然后才会去router中创建路由。

添加路由群组

注册属性之后,还可以创建路由群组,但是这时路由群组不允许添加属性action:

1
2
3
4
5
6
7
8
9
10
11
12
13
class RouteRegistrar
{
/**
* Create a route group with shared attributes.
*
* @param \Closure|string $callback
* @return void
*/
public function group($callback)
{
$this->router->group($this->attributes, $callback);
}
}

Router路由群组加载

路由群组的功能可以不断叠加递归,因此每次调用group,都要用新路由群组的属性与旧路由群组属性合并,以待新的路由去继承。group参数可以是闭包函数,也可以是包含定义路由的文件路径。

Illuminate\Routing\Router.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<?php

namespace Illuminate\Routing;

/**
* @mixin \Illuminate\Routing\RouteRegistrar
*/
class Router implements BindingRegistrar, RegistrarContract
{
/**
* Create a route group with shared attributes.
*
* @param array $attributes
* @param \Closure|string $routes
* @return void
*/
public function group(array $attributes, $routes)
{
$this->updateGroupStack($attributes);

// Once we have updated the group stack, we'll load the provided routes and
// merge in the group's attributes when the routes are created. After we
// have created the routes, we will pop the attributes off the stack.
$this->loadRoutes($routes);

array_pop($this->groupStack);
}

/**
* Update the group stack with the given attributes.
*
* @param array $attributes
* @return void
*/
protected function updateGroupStack(array $attributes)
{
if ($this->hasGroupStack()) {
$attributes = $this->mergeWithLastGroup($attributes);
}

$this->groupStack[] = $attributes;
}

/**
* Determine if the router currently has a group stack.
*
* @return bool
*/
public function hasGroupStack()
{
return ! empty($this->groupStack);
}

/**
* Merge the given array with the last group stack.
*
* @param array $new
* @param bool $prependExistingPrefix
* @return array
*/
public function mergeWithLastGroup($new, $prependExistingPrefix = true)
{
return RouteGroup::merge($new, end($this->groupStack), $prependExistingPrefix);
}
}

关于路由群组属性的合并:

  • prefix、as、namespace这几个属性会连接在一起,例如prefix1/prefix2/prefix3。
  • where属性数组相同的会被替换,不同的会被合并。
  • domain属性会被替换。
  • 其他属性,例如middleware数组会直接被合并,即使存在相同的元素。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php

namespace Illuminate\Routing;

use Illuminate\Support\Arr;

class RouteGroup
{
/**
* Merge route groups into a new array.
*
* @param array $new
* @param array $old
* @param bool $prependExistingPrefix
* @return array
*/
public static function merge($new, $old, $prependExistingPrefix = true)
{
if (isset($new['domain'])) {
unset($old['domain']);
}

$new = array_merge(static::formatAs($new, $old), [
'namespace' => static::formatNamespace($new, $old),
'prefix' => static::formatPrefix($new, $old, $prependExistingPrefix),
'where' => static::formatWhere($new, $old),
]);

return array_merge_recursive(Arr::except(
$old, ['namespace', 'prefix', 'where', 'as']
), $new);
}
}

Router路由加载

添加路由需要很多步骤,需要将路由本身的属性和路由群组的属性相结合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
class Router implements BindingRegistrar, RegistrarContract
{
/**
* Register a new GET route with the router.
*
* @param string $uri
* @param array|string|callable|null $action
* @return \Illuminate\Routing\Route
*/
public function get($uri, $action = null)
{
return $this->addRoute(['GET', 'HEAD'], $uri, $action);
}

/**
* Add a route to the underlying route collection.
*
* @param array|string $methods
* @param string $uri
* @param array|string|callable|null $action
* @return \Illuminate\Routing\Route
*/
public function addRoute($methods, $uri, $action)
{
return $this->routes->add($this->createRoute($methods, $uri, $action));
}

/**
* Create a new route instance.
*
* @param array|string $methods
* @param string $uri
* @param mixed $action
* @return \Illuminate\Routing\Route
*/
protected function createRoute($methods, $uri, $action)
{
// If the route is routing to a controller we will parse the route action into
// an acceptable array format before registering it and creating this route
// instance itself. We need to build the Closure that will call this out.
if ($this->actionReferencesController($action)) {
$action = $this->convertToControllerAction($action);
}

$route = $this->newRoute(
$methods, $this->prefix($uri), $action
);

// If we have groups that need to be merged, we will merge them now after this
// route has already been created and is ready to go. After we're done with
// the merge we will be ready to return the route back out to the caller.
if ($this->hasGroupStack()) {
$this->mergeGroupAttributesIntoRoute($route);
}

$this->addWhereClausesToRoute($route);

return $route;
}
}

从上面来看,添加一个新的路由需要:

  • 给路由的控制器添加group的namespace
  • 给路由的uri添加group的prefix前缀
  • 创建新的路由
  • 更新路由的属性信息
  • 为路由添加router-pattern正则约束
  • 路由添加到RouteCollection中

控制器namespace

路由控制器的命名空间一般不用特别指定,默认值是\App\Http\Controllers,每次创建新的路由,都要将默认的命名空间添加到控制器中去:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
protected function actionReferencesController($action)
{
if (! $action instanceof Closure) {
return is_string($action) || (isset($action['uses']) &&
is_string($action['uses']));
}
return false;
}
protected function convertToControllerAction($action)
{
if (is_string($action)) {
$action = ['uses' => $action];
}
if (! empty($this->groupStack)) {
$action['uses'] = $this->prependGroupNamespace($action['
uses']);
}
$action['controller'] = $action['uses'];
return $action;
}
protected function prependGroupNamespace($class)
{
$group = end($this->groupStack);
return isset($group['namespace']) && strpos($class, '\\') !=
= 0
? $group['namespace'].'\\'.$class : $class;
}

uri前缀

在创建新的路由前,需要将路由群组的prefix添加到路由的uri中:

1
2
3
4
5
6
7
8
9
10
11
12
13
protected function prefix($uri)
{
return trim(trim($this->getLastGroupPrefix(), '/').'/'.trim(
$uri, '/'), '/') ?: '/';
}
public function getLastGroupPrefix()
{
if (! empty($this->groupStack)) {
$last = end($this->groupStack);
return isset($last['prefix']) ? $last['prefix'] : '';
}
return '';
}

创建新的路由

路由的创建需要Route类:

1
2
3
4
5
6
protected function newRoute($methods, $uri, $action)
{
return (new Route($methods, $uri, $action))
->setRouter($this)
->setContainer($this->container);
}

关于Router类添加新的路由我们在下一部分详细说。

更新路由属性信息

创建新的路由之后,需要将路由本身的属性action与路由群组的属性结合在一起:

1
2
3
4
5
6
7
8
public function hasGroupStack()
{
return ! empty($this->groupStack);
}
protected function mergeGroupAttributesIntoRoute($route)
{
$route->setAction($this->mergeWithLastGroup($route->getAction()));
}

添加全局正则约束到路由

上一篇文章我们说过,我们可以为路由通过pattern方法添加全局的参数正则约束,所有每次添加新的路由都要将这个全局正则约束添加到路由中:

1
2
3
4
5
6
7
8
9
10
11
public function pattern($key, $pattern)
{
$this->patterns[$key] = $pattern;
}
protected function addWhereClausesToRoute($route)
{
$route->where(array_merge(
$this->patterns, isset($route->getAction()['where']) ? $route->getAction()['where'] : []
));
return $route;
}

Route路由加载

前面说过,路由的创建是由Route这个类完成的:

1
2
3
4
5
6
7
8
9
10
11
12
13
public function __construct($methods, $uri, $action)
{
$this->uri = $uri;
$this->methods = (array) $methods;
$this->action = $this->parseAction($action);
if (in_array('GET', $this->methods) && ! in_array('HEAD', $t
his->methods)) {
$this->methods[] = 'HEAD';
}
if (isset($this->action['prefix'])) {
$this->prefix($this->action['prefix']);
}
}

由此可以看出,路由的创建主要是路由的各个属性的初始化,其中值得注意的有两个:action与prefix action解析

1
2
3
4
protected function parseAction($action)
{
return RouteAction::parse($this->uri, $action);
}

我们可以看出,添加新的路由时,action属性需要利用RouteAction类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class RouteAction
{
public static function parse($uri, $action)
{
if (is_null($action)) {
return static::missingAction($uri);
}
if (is_callable($action)) {

return ['uses' => $action];
}
elseif (! isset($action['uses'])) {
$action['uses'] = static::findCallable($action);
}
if (is_string($action['uses']) && ! Str::contains($action['uses'], '@')) {
$action['uses'] = static::makeInvokable($action['uses']);
}
return $action;
}
protected static function findCallable(array $action)
{
return Arr::first($action, function ($value, $key) {
return is_callable($value) && is_numeric($key);
});
}
protected static function makeInvokable($action)
{
if (! method_exists($action, '__invoke')) {
throw new UnexpectedValueException("Invalid route ac
tion: [{$action}].");
}
return $action.'@__invoke';
}
}

前面的博客我们说过,创建路由的时候,除了为路由分配控制器之外,还可以为路由分配闭包函数,还有类函数,例如之前说的单动作控制器:

1
2
3
4
5
6
7
8
9
$router->get('foo/bar2', [‘domain’ => 'www.example.com', 'Illumi
nate\Tests\Routing\ActionStub']);
class ActionStub
{
public function __invoke()
{
return 'hello';
}
}

因此,解析action主要做两件事:

  • 为闭包函数添加use键。对于此时没有use键的路由,由于之前在Router中已经为控制器添加use键,因此这时没有use键的,必然是闭包函数,在这里直接或者在action中寻找闭包函数后,为闭包函数添加use键。
  • 单动作控制器添加__invoke 。对于单动作控制器来说,此时已经和控制器一样拥有’use’键,但是并没有@符号,此时就会调用makeInvokable函数来将__invoke添加到后面。

prefix前缀

路由自身也有prefix属性,而且这个属性要加在其他prefix的最前面,作为路由的uri:

1
2
3
4
5
6
public function prefix($prefix)
{
$uri = rtrim($prefix, '/').'/'.ltrim($this->uri, '/');
$this->uri = trim($uri, '/');
return $this;
}

Route路由属性加载

除了RouteRegistrar之外,Route也可以为路由添加属性:

prefix前缀

1
2
3
4
5
6
public function prefix($prefix)
{
$uri = rtrim($prefix, '/').'/'.ltrim($this->uri, '/');
$this->uri = trim($uri, '/');
return $this;
}

where正则约束

1
2
3
4
5
6
7
8
9
10
11
12
public function where($name, $expression = null)
{
foreach ($this->parseWhere($name, $expression) as $name => $
expression) {
$this->wheres[$name] = $expression;
}
return $this;
}
protected function parseWhere($name, $expression)
{
return is_array($name) ? $name : [$name => $expression];
}

middleware中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public function middleware($middleware = null)
{
if (is_null($middleware)) {
return (array) Arr::get($this->action, 'middleware', [])
;
}
if (is_string($middleware)) {
$middleware = func_get_args();
}
$this->action['middleware'] = array_merge(
(array) Arr::get($this->action, 'middleware', []), $middleware
);
return $this;
}

uses控制器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public function uses($action)
{
$action = is_string($action) ? $this->addGroupNamespaceToStringUses($action) : $action;

return $this->setAction(
array_merge(
$this->action,
$this->parseAction([
'uses' => $action,
'controller' => $action,
])
)
);
}

name命名

1
2
3
4
5
6
public function name($name)
{
$this->action['as'] = isset($this->action['as']) ? $this->action['as'].$name : $name;

return $this;
}

RouteCollection添加路由

在上面的部分,我们看到添加路由的代码:

1
2
3
4
protected function addRoute($methods, $uri, $action)
{
return $this->routes->add($this->createRoute($methods, $uri,$action));
}

新创建的路由会加入到RouteCollection中,会更新类中的routes、allRoutes、nameList、actionList。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<?php

namespace Illuminate\Routing;

use Illuminate\Container\Container;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;

class RouteCollection extends AbstractRouteCollection
{
/**
* Add a Route instance to the collection.
*
* @param \Illuminate\Routing\Route $route
* @return \Illuminate\Routing\Route
*/
public function add(Route $route)
{
$this->addToCollections($route);

$this->addLookups($route);

return $route;
}

/**
* Add the given route to the arrays of routes.
*
* @param \Illuminate\Routing\Route $route
* @return void
*/
protected function addToCollections($route)
{
$domainAndUri = $route->getDomain().$route->uri();

foreach ($route->methods() as $method) {
$this->routes[$method][$domainAndUri] = $route;
}

$this->allRoutes[$method.$domainAndUri] = $route;
}

/**
* Add the route to any look-up tables if necessary.
*
* @param \Illuminate\Routing\Route $route
* @return void
*/
protected function addLookups($route)
{
// If the route has a name, we will add it to the name look-up table so that we
// will quickly be able to find any route associate with a name and not have
// to iterate through every route every time we need to perform a look-up.
if ($name = $route->getName()) {
$this->nameList[$name] = $route;
}

// When the route is routing to a controller we will also store the action that
// is used by the route. This will let us reverse route to controllers while
// processing a request and easily generate URLs to the given controllers.
$action = $route->getAction();

if (isset($action['controller'])) {
$this->addToActionList($action, $route);
}
}

/**
* Add a route to the controller action dictionary.
*
* @param array $action
* @param \Illuminate\Routing\Route $route
* @return void
*/
protected function addToActionList($action, $route)
{
$this->actionList[trim($action['controller'], '\\')] = $route;
}
}

我们在上面路由的注册启动章节说道,路由的启动是namespace Illuminate\Foundation\Support\Providers\RouteServiceProvider完成的,调用的是boot函数:

1
2
3
4
5
6
7
8
9
10
11
12
public function boot()
{
$this->setRootControllerNamespace();
if ($this->app->routesAreCached()) {
$this->loadCachedRoutes();
} else {
$this->loadRoutes();
$this->app->booted(function () {
$this->app['router']->getRoutes()->refreshNameLookups();
});
}
}

在最后一句,程序将会在所有服务都启动后运行refreshNameLookups函数,把所有的name属性加载到RouteCollection中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class RouteCollection extends AbstractRouteCollection
{
/**
* A flattened array of all of the routes.
*
* @var \Illuminate\Routing\Route[]
*/
protected $allRoutes = [];

/**
* Refresh the name look-up table.
*
* This is done in case any names are fluently defined or if routes are overwritten.
*
* @return void
*/
public function refreshNameLookups()
{
$this->nameList = [];

foreach ($this->allRoutes as $route)
{
if ($route->getName())
{
$this->nameList[$route->getName()] = $route;
}
}
}
}

测试样例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public function testRouteCollectionCanRefreshNameLookups()
{
$routeIndex = new Route('GET', 'foo/index', ['uses' => 'FooController@index',]);

$this->assertNull($routeIndex->getName());

$this->routeCollection->add($routeIndex)->name('route_name');

$this->assertNull($this->routeCollection->getByName('route_name'));

$this->routeCollection->refreshNameLookups();

$this->assertEquals($routeIndex, $this->routeCollection->getByName('route_name'));
}

0%