路由解析
路由服务提供者
路由是外界访问Laravel应用程序的通路。那服务提供者顾名思义就是提供服务的。一般我们都是在 Routes 文件下面的路由文件中定义路由访问方法,路由访问方法定义到我们的识别的过程,肯定 是需要路由组件进行相关的处理,那这个组件是怎么被装入到容量里面的呢? 这个就是由服务提供者来完成的。
路由的加载
router这个服务是在实例化应用程序Application时在构造方法里通过注册 RoutingServiceProvider 时绑定到服务容器 里的:
public function __construct($basePath = null)
{
if ($basePath) {
$this->setBasePath($basePath);
}
$this->registerBaseBindings();
//注册服务提供者
$this->registerBaseServiceProviders();
$this->registerCoreContainerAliases();
}
protected function registerBaseServiceProviders()
{
$this->register(new EventServiceProvider($this));
$this->register(new LogServiceProvider($this));
//注册路由服务提供者
$this->register(new RoutingServiceProvider($this));
}
这个方法就是注册路由,我们点进去就可以看到
/**
* Register the router instance.
*
* @return void
*/
protected function registerRouter()
{
$this->app->singleton('router', function ($app) {
return new Router($app['events'], $app);
});
}
这是注册一个Router实例,调用的静态方法都对应于\Illuminate\Routing\Router类里的方法,这个类里 包含了与路由的注册、寻址、调度相关的方法。
既然路由的实例我们找到了,那么路由文件是怎么注册到容器中呢?
在\Illuminate\Routing\RoutingServiceProvider::register() 类中往容器对象的 $bindings 数组属性 以 keyvalue 形式注册进去,然后就会由服务提供者进行相关服务的注册,并通过引导服务( \Illuminate\Foundation\Bootstrap\BootProviders::class)下面的 Boot 方法来引导每个服务的执行操作
调用 \App\Providers\RouteServiceProvider::boot() 方法加载路由列表。在这当中会调用父类的boot方 法,获取到先去容器里面的Router的注册类 laravel 首先去寻找路由的缓存文件,没有缓存文件再去进行加载路由。
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();
});
}
}
//加载 \App\Providers\RouteServiceProvider中的map方法
protected function loadRoutes()
{
if (method_exists($this, 'map')) {
$this->app->call([$this, 'map']);
}
}
调用 \App\Providers\RouteServiceProvider的map方法
通过map方法我们能看到 laravel将路由分为两个大组:api、web。这两个部分的路由分别写在两个文件中:routes/web.php、 routes/api.php。
路由的注册
通常都是用Route这个Facade调用静态方法get, post, head, options, put, patch, delete…等来注册路由。而Router这个实例我们第一步的时候就已经注册好了。
public function get($uri, $action = null)
{
return $this->addRoute(['GET', 'HEAD'], $uri, $action);
}
public function post($uri, $action = null)
{
return $this->addRoute('POST', $uri, $action);
}
其实这些方法都是调用addRoute这个方法
public function addRoute($methods, $uri, $action)
{
return $this->routes->add($this->createRoute($methods, $uri, $action));
}
再里面首先要先“createRoute”之后再“add”
protected function createRoute($methods, $uri, $action)
{
//判断当前路由是不是控制器的方法
if ($this->actionReferencesController($action)) {
//controller@action类型的路由在这类转换
$action = $this->convertToControllerAction($action);
}
$route = $this->newRoute(
$methods, $this->prefix($uri), $action
);
// 合并路由分组
if ($this->hasGroupStack()) {
$this->mergeGroupAttributesIntoRoute($route);
}
//添加路由条件
$this->addWhereClausesToRoute($route);
return $route;
}
在createRoute方法里面转换了形式
经过转换之后,我们的路由变成这样的形式
[
'uses' => 'App\Http\Controllers\HomeController@index',
'controller' => 'App\Http\Controllers\HomeController@index'
]
再看add方法
public function add(Route $route)
{
//添加到集合
$this->addToCollections($route);
//添加到查找表中
$this->addLookups($route);
return $route;
}
protected function addToCollections($route)
{
$domainAndUri = $route->getDomain().$route->uri();
foreach ($route->methods() as $method) {
$this->routes[$method][$domainAndUri] = $route;
}
$this->allRoutes[$method.$domainAndUri] = $route;
}
protected function addLookups($route)
{
if ($name = $route->getName()) {
$this->nameList[$name] = $route;
}
$action = $route->getAction();
if (isset($action['controller'])) {
$this->addToActionList($action, $route);
}
}
经过这步操作,我们的路由变成了这种,放在下面4个属性表里面——routes、allRoutes、nameList、actionList
routes中存放了HTTP请求方法与路由对象的映射:
[
'GET' => [ $routeUri1 => $routeObj1
...
]
...
]
allRoutes属性里存放的内容时将routes属性里的二位数组变成一位数组后的内容:
[
'GET' . $routeUri1 => $routeObj1
'GET' . $routeUri2 => $routeObj2
...
]
nameList是路由名称与路由对象的一个映射表
[
$routeName1 => $routeObj1
...
]
actionList是路由控制器方法字符串与路由对象的映射表
[
'App\Http\Controllers\ControllerOne@ActionOne' => $routeObj1
]
至此路由的注册就已经完成,下面就是最后一步,匹配相应的路由
路由的分发
路由查找是由HTTP请求在经过Pipeline通道上的中间件的前置操作后到达目的地:
这个方法就是请求经过一些中间件的验证,执行dispatchToRouter()方法。
最终路由的匹配就是在这个函数中完成的。点进去,发现先findRoute(),之后runRoute()
protected function findRoute($request)
{
$this->current = $route = $this->routes->match($request);
$this->container->instance(Route::class, $route);
return $route;
}
protected function runRoute(Request $request, Route $route)
{
$request->setRouteResolver(function () use ($route) {
return $route;
});
$this->events->dispatch(new Events\RouteMatched($route, $request));
return $this->prepareResponse($request,
$this->runRouteWithinStack($route, $request)
);
}
这里面要经过一些验证,验证成功之后需routes中存放了HTTP请求方法与路由对象的映射。
路由参数绑定
当路由通过了全部的认证就将会被返回,接下来就要将请求对象URI里的路径参数绑定赋值给路由参数
public function bind(Request $request)
{
$this->compileRoute();
$this->parameters = (new RouteParameterBinder($this))
->parameters($request);
$this->originalParameters = $this->parameters;
return $this;
}
经过绑定之后,紧接着就时候运行通过匹配路由中对应的控制器方法并返回响应对 象了
路由的寻址过程就结束了。
简化的整体流程图