06. RouteServiceProvider 详解
Opened this issue · 0 comments
RouteServiceProvider 详解
在
RouteServiceProvider::boot
阶段,所有路由都只是执行登记,匹配的逻辑是在 02. HTTP Kernel Handle解析 的 dispatchToRouter 阶段。
我们先找到 RouteServiceProvider.php
在 config/app.php
的 providers
中定义的 RouteServiceProvider 其实是:
Line 161 in 6d9215c
App\Providers\RouteServiceProvider::class, |
而这个类其实继承自
Illuminate\Foundation\Support\Providers\RouteServiceProvider
laravel/app/Providers/RouteServiceProvider.php
Lines 6 to 9 in 6d9215c
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; | |
class RouteServiceProvider extends ServiceProvider | |
{ |
我们分析 Illuminate\Foundation\Support\Providers\RouteServiceProvider
的代码
register() 阶段
其 register
方法是空的
Lines 81 to 89 in d081c91
/** | |
* Register the service provider. | |
* | |
* @return void | |
*/ | |
public function register() | |
{ | |
// | |
} |
boot() 阶段
而在 boot
阶段,几乎运行了 RouteServiceProvider
中的所有方法
Lines 24 to 43 in d081c91
/** | |
* Bootstrap any application services. | |
* | |
* @return void | |
*/ | |
public function boot() | |
{ | |
$this->setRootControllerNamespace(); | |
if ($this->app->routesAreCached()) { | |
$this->loadCachedRoutes(); | |
} else { | |
$this->loadRoutes(); | |
$this->app->booted(function () { | |
$this->app['router']->getRoutes()->refreshNameLookups(); | |
$this->app['router']->getRoutes()->refreshActionLookups(); | |
}); | |
} | |
} |
一. setRootControllerNamespace()
Lines 45 to 55 in d081c91
/** | |
* Set the root controller namespace for the application. | |
* | |
* @return void | |
*/ | |
protected function setRootControllerNamespace() | |
{ | |
if (! is_null($this->namespace)) { | |
$this->app[UrlGenerator::class]->setRootControllerNamespace($this->namespace); | |
} | |
} |
二. 判断路由是否缓存过
Illuminate\Foundation\Application::routesAreCached
,
其实就是判断文件 bootstrap/cache/routes.php
是否存在
laravel/vendor/laravel/framework/src/Illuminate/Foundation/Application.php
Lines 895 to 903 in d081c91
/** | |
* Determine if the application routes are cached. | |
* | |
* @return bool | |
*/ | |
public function routesAreCached() | |
{ | |
return $this['files']->exists($this->getCachedRoutesPath()); | |
} |
laravel/vendor/laravel/framework/src/Illuminate/Foundation/Application.php
Lines 905 to 913 in d081c91
/** | |
* Get the path to the routes cache file. | |
* | |
* @return string | |
*/ | |
public function getCachedRoutesPath() | |
{ | |
return $this->bootstrapPath().'/cache/routes.php'; | |
} |
存在则直接加载
Lines 57 to 67 in d081c91
/** | |
* Load the cached routes for the application. | |
* | |
* @return void | |
*/ | |
protected function loadCachedRoutes() | |
{ | |
$this->app->booted(function () { | |
require $this->app->getCachedRoutesPath(); | |
}); | |
} |
三. 如果没有缓存,则遍历路由
Lines 69 to 79 in d081c91
/** | |
* Load the application routes. | |
* | |
* @return void | |
*/ | |
protected function loadRoutes() | |
{ | |
if (method_exists($this, 'map')) { | |
$this->app->call([$this, 'map']); | |
} | |
} |
map
方法在这里laravel/app/Providers/RouteServiceProvider.php
Lines 31 to 43 in d081c91
/** | |
* Define the routes for the application. | |
* | |
* @return void | |
*/ | |
public function map() | |
{ | |
$this->mapApiRoutes(); | |
$this->mapWebRoutes(); | |
// | |
} |
laravel/app/Providers/RouteServiceProvider.php
Lines 45 to 72 in d081c91
/** | |
* Define the "web" routes for the application. | |
* | |
* These routes all receive session state, CSRF protection, etc. | |
* | |
* @return void | |
*/ | |
protected function mapWebRoutes() | |
{ | |
Route::middleware('web') | |
->namespace($this->namespace) | |
->group(base_path('routes/web.php')); | |
} | |
/** | |
* Define the "api" routes for the application. | |
* | |
* These routes are typically stateless. | |
* | |
* @return void | |
*/ | |
protected function mapApiRoutes() | |
{ | |
Route::prefix('api') | |
->middleware('api') | |
->namespace($this->namespace) | |
->group(base_path('routes/api.php')); | |
} |
这里的
mapWebRoutes
和 mapApiRoutes
是分别将 routes/web.php 和 routes/api.php 用 Route
门面类的 group 加载了一遍。 Route::group
实质运行到的是 Illuminate\Routing\Router::group
(关于门面类的文章请见 [TODO]),代码为laravel/vendor/laravel/framework/src/Illuminate/Routing/Router.php
Lines 356 to 416 in d081c91
/** | |
* 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 (! empty($this->groupStack)) { | |
$attributes = RouteGroup::merge($attributes, end($this->groupStack)); | |
} | |
$this->groupStack[] = $attributes; | |
} | |
/** | |
* Merge the given array with the last group stack. | |
* | |
* @param array $new | |
* @return array | |
*/ | |
public function mergeWithLastGroup($new) | |
{ | |
return RouteGroup::merge($new, end($this->groupStack)); | |
} | |
/** | |
* Load the provided routes. | |
* | |
* @param \Closure|string $routes | |
* @return void | |
*/ | |
protected function loadRoutes($routes) | |
{ | |
if ($routes instanceof Closure) { | |
$routes($this); | |
} else { | |
$router = $this; | |
require $routes; | |
} | |
} |
上面的逻辑是一层层剥开
Route::group
去执行里面的 Route::get
/ Route::post
...laravel/vendor/laravel/framework/src/Illuminate/Routing/Router.php
Lines 133 to 214 in d081c91
* Register a new GET route with the router. | |
* | |
* @param string $uri | |
* @param \Closure|array|string|null $action | |
* @return \Illuminate\Routing\Route | |
*/ | |
public function get($uri, $action = null) | |
{ | |
return $this->addRoute(['GET', 'HEAD'], $uri, $action); | |
} | |
/** | |
* Register a new POST route with the router. | |
* | |
* @param string $uri | |
* @param \Closure|array|string|null $action | |
* @return \Illuminate\Routing\Route | |
*/ | |
public function post($uri, $action = null) | |
{ | |
return $this->addRoute('POST', $uri, $action); | |
} | |
/** | |
* Register a new PUT route with the router. | |
* | |
* @param string $uri | |
* @param \Closure|array|string|null $action | |
* @return \Illuminate\Routing\Route | |
*/ | |
public function put($uri, $action = null) | |
{ | |
return $this->addRoute('PUT', $uri, $action); | |
} | |
/** | |
* Register a new PATCH route with the router. | |
* | |
* @param string $uri | |
* @param \Closure|array|string|null $action | |
* @return \Illuminate\Routing\Route | |
*/ | |
public function patch($uri, $action = null) | |
{ | |
return $this->addRoute('PATCH', $uri, $action); | |
} | |
/** | |
* Register a new DELETE route with the router. | |
* | |
* @param string $uri | |
* @param \Closure|array|string|null $action | |
* @return \Illuminate\Routing\Route | |
*/ | |
public function delete($uri, $action = null) | |
{ | |
return $this->addRoute('DELETE', $uri, $action); | |
} | |
/** | |
* Register a new OPTIONS route with the router. | |
* | |
* @param string $uri | |
* @param \Closure|array|string|null $action | |
* @return \Illuminate\Routing\Route | |
*/ | |
public function options($uri, $action = null) | |
{ | |
return $this->addRoute('OPTIONS', $uri, $action); | |
} | |
/** | |
* Register a new route responding to all verbs. | |
* | |
* @param string $uri | |
* @param \Closure|array|string|null $action | |
* @return \Illuminate\Routing\Route | |
*/ | |
public function any($uri, $action = null) | |
{ | |
return $this->addRoute(self::$verbs, $uri, $action); | |
} |
不难发现,不管是 GET/POST... 还是 any,都只是调用了
addRoute
将这个路由的属性登记了一下:laravel/vendor/laravel/framework/src/Illuminate/Routing/Router.php
Lines 434 to 445 in d081c91
/** | |
* Add a route to the underlying route collection. | |
* | |
* @param array|string $methods | |
* @param string $uri | |
* @param \Closure|array|string|null $action | |
* @return \Illuminate\Routing\Route | |
*/ | |
public function addRoute($methods, $uri, $action) | |
{ | |
return $this->routes->add($this->createRoute($methods, $uri, $action)); | |
} |
$this->routes
是一个 \Illuminate\Routing\RouteCollection
的集合类,add
由这个方法生成的路由laravel/vendor/laravel/framework/src/Illuminate/Routing/Router.php
Lines 447 to 478 in d081c91
/** | |
* 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; | |
} |
四. dispatch() -> runRoute() 阶段
路由登记完成后,就是 Kernel 触发管道层层剥洋葱调用中间件最后触发路由 dispatch (当然,这已经运行到 RouteServiceProvider 的外面了)。
剥洋葱的过程请查阅 05. Pipeline 解析
laravel/vendor/laravel/framework/src/Illuminate/Routing/Router.php
Lines 660 to 682 in d081c91
/** | |
* Run the given route within a Stack "onion" instance. | |
* | |
* @param \Illuminate\Routing\Route $route | |
* @param \Illuminate\Http\Request $request | |
* @return mixed | |
*/ | |
protected function runRouteWithinStack(Route $route, Request $request) | |
{ | |
$shouldSkipMiddleware = $this->container->bound('middleware.disable') && | |
$this->container->make('middleware.disable') === true; | |
$middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route); | |
return (new Pipeline($this->container)) | |
->send($request) | |
->through($middleware) | |
->then(function ($request) use ($route) { | |
return $this->prepareResponse( | |
$request, $route->run() | |
); | |
}); | |
} |
第679行的 $route->run
是至关重要的,调用到了 controller (本质其实是使用 09. 容器的依赖注入机制 将路由方法所依赖参数解析出来,并运行)
laravel/vendor/laravel/framework/src/Illuminate/Routing/Route.php
Lines 158 to 176 in d081c91
/** | |
* Run the route action and return the response. | |
* | |
* @return mixed | |
*/ | |
public function run() | |
{ | |
$this->container = $this->container ?: new Container; | |
try { | |
if ($this->isControllerAction()) { | |
return $this->runController(); | |
} | |
return $this->runCallable(); | |
} catch (HttpResponseException $e) { | |
return $e->getResponse(); | |
} | |
} |