laravel路由解析全解

今天来详细的跟大家分享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路由的解析和执行过程,当然十分不容易理解,所以需要自己耐心钻研一下,因为一千个人眼中有一千个哈姆雷特!!加油

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值