今天来详细的跟大家分享laravel框架中的路由到底是如何解析的~
第一步,我想先让大家知道路由原本的样子和解析后的样子
原:Route::get('/home', 'HomeController@index')->name('home');
解析后:app\controllers\HomeController@index
大体的整个执行过程是这样的:(读这篇文章之前大家可以先了解laravel的服务容器和服务提供者)
先注册路由服务(app.php)——>执行启动路由(对应类里的boot方法),在App\Providers\RouteServiceProvider中
public function boot()
{
//
parent::boot();
}
//父类
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();
});
}
}
在boot方法中,首先通过setRootControllerNamespace()方法设置根命名空间,即
protected function setRootControllerNamespace()
{
if (! is_null($this->namespace)) {
$this->app[UrlGenerator::class]->setRootControllerNamespace($this->namespace);
}
}
然后下面判断缓存中是否有设置当前路由,有的话就直接取,没有就执行else里面的loadRoutes()方法,
//加载应用路由
protected function loadRoutes()
{
if (method_exists($this, 'map')) {
$this->app->call([$this, 'map']);
}
}
在loadRoutes里面来调用map方法,
定义应用程序的路由。
public function map()
{
$this->mapApiRoutes();
$this->mapWebRoutes();
}
//web
protected function mapWebRoutes()
{
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web.php'));
}
然后加载中间件,定义命名空间,匹配路由,然后实现路由加载。
希望以上这些可以让大家对laravel的路由解析有个宏观上的概念。下面我们继续往下细看:
我们找到group方法,在Illuminate\Routing\Router类中,
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.
//一旦更新了组堆栈,在创建路由时,我们将在组的属性中加载提供的路由和/Merge。在我们/创建了路由之后,我们将弹出堆栈中的属性。
$this->loadRoutes($routes); //引入路由文件 如:web.php
array_pop($this->groupStack);
}
在group方法里面,$routes就是上面的base_path('routes/web.php')。
首先执行updateGroupStack()方法更新路由堆栈
protected function updateGroupStack(array $attributes)
{
if (! empty($this->groupStack)) {
//在这里将新的属性和旧的属性记性组合
$attributes = RouteGroup::merge($attributes, end($this->groupStack));
}
$this->groupStack[] = $attributes;
}
然后用loadRoutes方法加载引入路由文件routes/web.php,
protected function loadRoutes($routes)
{
if ($routes instanceof Closure) {
$routes($this);
} else {
$router = $this;
require $routes;
}
}
在结合刚才的web中间件和namespace方法,就实现了解析路由。
然后我们来看,他是如何生成路由的:
你当时定义的路由都是Route::get('/home', 'HomeController@index')->name('home');这样子的。Route代表App\Providers\RouteServiceProvider服务。
里面有对应的get、post、delete等等;
public function get($uri, $action = null)
{
return $this->addRoute(['GET', 'HEAD'], $uri, $action);
}
protected function addRoute($methods, $uri, $action)
{
return $this->routes->add($this->createRoute($methods, $uri, $action));
}
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;
}
actionReferencesController方法
//确定操作是否正在路由到控制器
protected function actionReferencesController($action)
{
if (! $action instanceof Closure) {
return is_string($action) || (isset($action['uses']) && is_string($action['uses']));
}
return false;
}
convertToControllerAction方法
protected function convertToControllerAction($action)
{
if (is_string($action)) {
$action = ['uses' => $action];
}
//在这里,如果有必要,我们将合并任何组“Use”语句,以便操作/具有该属性的正确子句。然后,我们可以简单地在操作上设置控制器的名称/,并返回操作数组以供使用。
if (! empty($this->groupStack)) {
$action['uses'] = $this->prependGroupNamespace($action['uses']);
}
//在这里,我们将在操作数组上设置这个控制器名称,这样如果需要的话,我们总是有一个它的副本作为参考。这可以在我们搜索控制器名称或执行其他类型的提取操作时使用
$action['controller'] = $action['uses'];
return $action;
}
newRoute方法
//创建一个新的路由对象
protected function newRoute($methods, $uri, $action)
{
return (new Route($methods, $uri, $action))
->setRouter($this)
->setContainer($this->container);
}
以上就是定义后是如何生成路由的。
下面来执行路由:
laravel框架是先经过http内核处理,然后确定需要加载的服务,先注册后启动,然后执行对应内容,咱们知道到执行这一步
在App\Http\Kernel.php继承的父类Illuminate\Foundation\Http\Kernel中,会执行
protected function dispatchToRouter()
{
return function ($request) {
$this->app->instance('request', $request);
return $this->router->dispatch($request); //$this->router为路由的容器,调用路由服务类里面的dispatch方法
};
}
然后,我们回到路由服务类里面Router,找到该方法
//将 HTTP 请求分发到应用程序
public function dispatch(Request $request)
{
$this->currentRequest = $request;
return $this->dispatchToRoute($request); //将请求分发到路由,并返回响应。
}
public function dispatchToRoute(Request $request)
{
//首先,我们将找到一条与此请求相匹配的路径。我们还将在请求中设置路由解析器,以便分配给路由的中间件将接收对此路由实例的访问,以检查参数。
$route = $this->findRoute($request); //找到对应请求路由
$request->setRouteResolver(function () use ($route) {
return $route;
});
$this->events->dispatch(new Events\RouteMatched($route, $request));
$response = $this->runRouteWithinStack($route, $request);
return $this->prepareResponse($request, $response);
}
在上面代码中可以看到,是通过finddRoute来查找路由的
//查找与请求 request 匹配的路由
protected function findRoute($request)
{
// 从 RouteCollection(由 Router::get('/', callback) 等设置的路由) 集合中查找与 $request uri 相匹配的路由配置。
$this->current = $route = $this->routes->match($request); //匹配路由并返回给当前路由current
$this->container->instance(Route::class, $route); //通过容器得到当前路由实例
return $route;
}
在刚才的dispatchToRoute方法中,是通过runRouteWithinStack()方法来运行路由的
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() /****关键****/
);
});
}
可以看到,最终是run方法来运行的,run方法在Illuminate\Routing\Route类
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();
}
}
运行控制器里的方法,在Illuminate\Routing\ControllerDispatcher
public function dispatch(Route $route, $controller, $method)
{
$parameters = $this->resolveClassMethodDependencies(
$route->parametersWithoutNulls(), $controller, $method
);
if (method_exists($controller, 'callAction')) {
return $controller->callAction($method, $parameters);
}
return $controller->{$method}(...array_values($parameters));
}
以上就是laravel路由的解析和执行过程,当然十分不容易理解,所以需要自己耐心钻研一下,因为一千个人眼中有一千个哈姆雷特!!加油