In this article we will look at an important topic in the Laravel project – “routing”.

Almost all frameworks involve “routing,” which simply assigns the URL requested by the user to the corresponding handler.

So what are you waiting for? Get in the car!

Route Loading Principle

In this section we will focus on how to load the web.php routing configuration file that we defined in the Routes directory (just consider a typical Web application).

Preliminary knowledge

In Laravel, all services are bound to the “Laralvel service container” via the “service provider” register method before being used in Laravel projects.

As you might expect, loading a routing file is essentially a service that loads the route defined in the routing file into the Laravel kernel, then matches the correct route and processes the HTTP request. Therefore, we should find the routing related “service provider” to register and start the routing related service.

Now let us into the config/app. PHP will nodes in the configuration file associated with the routing to find “service provider”, that’s right is app \ will \ RouteServiceProvider: : class class.

Tip: For details on how service providers work, you can read “An in-depth look at The Laravel Service Provider Implementation,” which provides an in-depth look at service provider registration and startup. Do not know this friend can follow up to supplement this knowledge.

It is necessary to briefly describe the loading and execution of the “service provider” :

  1. First, the HTTP kernel performs all the “service provider” register methods to register all the services into the service container.
  2. After all service providers are registered, the boot method that has been registered is used to start the service.

Registration and startup of the “service provider” is handled by Illuminate Foundation Http Kernel.

After understanding the basic concept of “service provider”, it is not difficult to know the RouteServiceProvider route provider service, which is also processed by register and boot to load the service.

Drill down into the RouteServiceProvider service provider

Going into the RouteServiceProvider source code, let’s see how it actually works to load the routing configuration at registration and startup time.

<? php namespace App\Providers; use Illuminate\Support\Facades\Route; use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; /** * @see https://github.com/laravel/laravel/blob/5994e242152764a3aeabd5d88650526aeb793b90/app/Providers/RouteServiceProvider.php */ class RouteServiceProvider extends ServiceProvider { /** * This namespace is applied to your controller routes. Defines the namespace for the current Laravel application controller route. */ protected $namespace = 'App\Http\Controllers'; /** * Define your route model bindings, pattern filters, etc. Define route binding and regular filtering. */ public function boot() { parent::boot(); } /** * Define the routes for the application. Define application routes. */ public function map() { $this->mapApiRoutes(); $this->mapWebRoutes(); } /** * Define the "web" routes for the application. Define application Web routing. * * These routes all receive session state, CSRF protection, etc. All routes defined here handle things like session state and CSRF protection. */ protected function mapWebRoutes() { Route::middleware('web') ->namespace($this->namespace) ->group(base_path('routes/web.php')); } /** * Define the "api" routes for the application. Define application API routing. * * These routes are typically stateless. The route defined here is a typical stateless route. */ protected function mapApiRoutes() { Route::prefix('api') ->middleware('api') ->namespace($this->namespace) ->group(base_path('routes/api.php')); }}Copy the code

Yes, it’s easy to read. I removed some comments and blank lines from the source code.

So, we just need to focus on the RouteServiceProvider’s boot method, which in its method body simply calls the parent’s Boot method to start the service.

In addition, the mapXXX() family of methods is declared inside the class. These methods are used to define the actual operations of the application’s routing, and the map family of functions will be explained later.

Let’s first look at how the Illuminate Foundation Support Providers RouteServiceProvider parent class handles the boot service:

<? php namespace Illuminate\Foundation\Support\Providers; use Illuminate\Routing\Router; use Illuminate\Support\ServiceProvider; use Illuminate\Contracts\Routing\UrlGenerator; /** * @mixin \Illuminate\Routing\Router * @see https://github.com/laravel/framework/blob/5.4/src/Illuminate/Foundation/Support/Providers/RouteServiceProvider.php * / class RouteServiceProvider extends ServiceProvider { /** * Bootstrap any application services. */ 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(); }); } } /** * Set the root controller namespace for the application. Set the root namespace of the application controller. */ protected function setRootControllerNamespace() { if (! is_null($this->namespace)) { $this->app[UrlGenerator::class]->setRootControllerNamespace($this->namespace); } } /** * Load the cached routes for the application. Load the route from the cache. */ protected function loadCachedRoutes() { $this->app->booted(function () { require $this->app->getCachedRoutesPath(); }); } /** * Load the application routes. Load application routes. */ protected function loadRoutes() {// Load application routes by calling the service container's call class App \ \ will \ \ RouteServiceProvider: : class reading configuration map method. if (method_exists($this, 'map')) { $this->app->call([$this, 'map']); }}}Copy the code

The routing Service Provider startup process is divided into the following steps:

  1. Set the namespace of the controller in our Laravel application to URL Generator (UrlGenerator) for future use;

  2. For system performance reasons, route caching is first detected. If the route is cached, the route configuration is read directly from the cache file.

  3. If no caching is performed, the loadRoutes method performs caching. The route loading process is eventually returned to the Map method defined in the App\Providers\RouteServiceProvider class.

By now, you should have established a relatively macro concept of the entire routing loading process.

Take a closer look at the route family defined by Map

Having established the macro route loading process, we took a step further and delve further into the mapXXX() family of methods, as these are the components that actually perform the route loading processing.

In the previous source code listing, we saw that the mapWebRoutes() and mapApiRoutes() methods are called and executed inside the Map method, which loads the Web and Api routing configurations, respectively.

Due to space constraints, we will only parse the loading mechanism of the Web route mapWebRoutes here, because the loading process of the two routes is almost exactly the same, right my friend?

. /** * Define the "web" routes for the application. Define application Web routing. * * These routes all receive session state, CSRF protection, etc. All routes defined here handle things like session state and CSRF protection. */ protected function mapWebRoutes() { Route::middleware('web') ->namespace($this->namespace) ->group(base_path('routes/web.php')); }...Copy the code

MapWebRoutes processes WebRoute loads through the Illuminate\Routing\Router service, which is represented by the Route Facade:

  1. Execute Route:: Middleware (‘web’) to register web middleware with the Route;

  2. $this->namespace ($this->namespace);

  3. Finally, run the group method using the base_path(‘routes/web.php’) directory of the routing file to set the Web routing group.

So let’s move on and see how it performs middleware and so on!

Open the interior of the Illuminate\Routing\Router class, the service on the Router facade, and you may not find the Middleware method declaration.

That’s right, it dynamically performs reflection by implementing the __call magic method, completing calls to middleware methods, and returning RouteRegistrar instances.

<? php namespace Illuminate\Routing; / * * * * @ see https://github.com/laravel/framework/blob/5.6/src/Illuminate/Routing/Router.php/class Router implements RegistrarContract, BindingRegistrar { /** * The route group attribute stack. */ protected $groupStack = []; /** * Create a route group with shared attributes. Create routing groups with common attributes (middleware, namespace, and so on). */ 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. Updates the given attributes (middleware, namespace, and so on) to the routing group stack. */ protected function updateGroupStack(array $attributes) { if (! empty($this->groupStack)) { $attributes = RouteGroup::merge($attributes, end($this->groupStack)); } $this->groupStack[] = $attributes; } /** * Load the provided routes. Load the routing of the definition of * * @ param \ Closure | string $routes * @ return void * / protected function loadRoutes ($routes) {if ($routes instanceof Closure) { $routes($this); } else { $router = $this; require $routes; } } /** * Dynamically handle calls into the router instance. Handle method calls in the Router instance dynamically. */ public function __call($method, $parameters) { // if (static::hasMacro($method)) { // return $this->macroCall($method, $parameters); } // See here, where the Middleware method is dynamically called by reflection, If ($method == 'middleware') {return (new RouteRegistrar($this))->attribute($method, $this) is_array($parameters[0]) ? $parameters[0] : $parameters); } // return (new RouteRegistrar($this))->attribute($method, $parameters[0]); }}Copy the code

Due to space constraints, this article will not explore the RouteRegistrar source code, but you can do it yourself.

To summarize, the Illuminate\Routing\Router::group method is called inside the RouteRegistrar::group method to load the Routing file.

Finally, the import of the Routing file is performed in the Illuminate\Routing\Router::group method:

  1. Update the attributes in the routing group using the updateGroupStack method (Route:: Middleware (…)). ->namespace(…) Setup middleware and namespaces, etc.);
  2. Use the loadRoutes method to introduce the routes defined in the base_path(‘routes/web.php’) file.

To this we will complete the analysis of the load process of the routing file, due to the module more involved, but also need readers to consider the friends to digest.

Tip: In Laravel, facade is a mechanism that provides easy access to Laravel services using static methods. Those who are not familiar with “Facade” can read “Laravel’s Facade system”.

Routing distribution

In this section we will focus on how HTTP is dispatched to the relevant route and performs a callback (or controller) for the route Settings.

If you’ve read about the Laravel lifecycle, you know that all HTTP requests are handled by the Illuminate Foundation HTTP kernel::class kernel, Capturing HTTP requests is located in the project entry file public/index.php.

Receiving HTTP Requests

/*
|--------------------------------------------------------------------------
| Run The Application
|--------------------------------------------------------------------------
|
| Once we have the application, we can handle the incoming request
| through the kernel, and send the associated response back to
| the client's browser allowing them to enjoy the creative
| and wonderful application we have prepared for them.
|
*/

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

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

To be specific, resolve the Illuminate\Contracts\Http\Kernel::class service instance from the service container and then execute the handle method of the service to handle the Http request.

This article does not explain how to capture an HTTP Request, illuminating illuminating \ HTTP \Request:: Capture () will be explained in detail ina future article as a supplement to this article if possible. But all you need to know here is that our Handle accepts the user’s Request as an argument and executes it.

So we need to drill down to Handle to see how HTTP requests are routed and handled by callbacks (or controllers).

HTTP requests are handled by the HTTP kernel

Leaving out the N parses here, well, we found the Illuminate Foundation Http kernel::class service instance, which should be easy for you.

<? php namespace Illuminate\Foundation\Http; use Exception; use Throwable; use Illuminate\Routing\Router; use Illuminate\Routing\Pipeline; use Illuminate\Support\Facades\Facade; use Illuminate\Contracts\Debug\ExceptionHandler; use Illuminate\Contracts\Foundation\Application; use Illuminate\Contracts\Http\Kernel as KernelContract; use Symfony\Component\Debug\Exception\FatalThrowableError; class Kernel implements KernelContract { /** * Handle an incoming HTTP request. Handle the HTTP request * * * @ see https://github.com/laravel/framework/blob/5.6/src/Illuminate/Foundation/Http/Kernel.php#L111 @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function handle($request) { try {  $request->enableHttpMethodParameterOverride(); $response = $this->sendRequestThroughRouter($request); } catch (Exception $e) { ... } catch (Throwable $e) { ... } $this->app['events']->dispatch( new Events\RequestHandled($request, $response) ); return $response; } /** * Send the given request through the middleware / router. Send user requests to the middleware and route * * @param \Illuminate\Http\Request $Request * @return \Illuminate\Http\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()); } /** * Get the route dispatcher callback. Access to distributed routing callback (or controller) * * @ see https://github.com/laravel/framework/blob/5.6/src/Illuminate/Foundation/Http/Kernel.php#L171 @return \Closure */ protected function dispatchToRouter() { return function ($request) { $this->app->instance('request',  $request); return $this->router->dispatch($request); }; }}Copy the code

There are several stages in the process of processing an HTTP request:

  1. Clear resolved requests (clearResolvedInstance);
  2. To perform the application bootstrap, see the service Provider Startup Principles summary for an in-depth look at the Laravel service Provider implementation.
  3. The request is sent to the middleware and routing, which is done by the Pipeline component.

For the treatment of the first two stages, you can read my related articles. In addition to two articles about middleware Laravel middleware principle and Laravel pipeline flow principle, you can go to study how Laravel middleware works.

Route Distribution processing

$this->dispatchToRouter(); $this->dispatchToRouter(); So now, let’s look at how dispatchToRouter distributes routes.

<?php
...
    protected function dispatchToRouter()
    {
        return function ($request) {
            $this->app->instance('request', $request);

            return $this->router->dispatch($request);
        };
    }
...Copy the code

From this source, we know that the dispatch route receives the $Request instance and dispatches it, which is returned to the Illuminate\Routing\Router service:

<? php namespace Illuminate\Routing; . class Router implements RegistrarContract, BindingRegistrar { ... /** * Dispatch the request to the application. Distribute HTTP requests to the application. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse */ public  function dispatch(Request $request) { $this->currentRequest = $request; return $this->dispatchToRoute($request); } /** * Dispatch the request to a route and return the response. The request is dispatched to the route and a response is returned. * * @param \Illuminate\Http\Request $request * @return mixed */ public function dispatchToRoute(Request $request) { return $this->runRoute($request, $this->findRoute($request)); } /** * Find the route matching a given request. Find the route that matches the request request. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Routing\Route */ protected function FindRoute ($request) {// Looks for a route from the RouteCollection set by Router::get('/', callback) and so on. $this->current = $route = $this->routes->match($request); $this->container->instance(Route::class, $route); return $route; } /** * Return the response for the given route. The closure (or controller) that performs the route configuration returns $response. * * @param Route $route * @param Request $request * @return mixed */ 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) ); } /** * Run the given route within a Stack "onion" instance. When a given route is run, middleware processes such as this are handled (the middleware here is a local route that applies only to the current route or routing group, which is different from the route in Kernel Handle). * * @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(// $route->run() will run the current route closure (or controller) to generate the result execution. $request, $route->run() ); }); } /** * Create a response instance from the given value. * * @param \Symfony\Component\HttpFoundation\Request $request *  @param mixed $response * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse */ public function prepareResponse($request, $response) { return static::toResponse($request, $response); }... }Copy the code

Route Distribution Process

Illuminate\Routing\Router service will receive the request that is dispatched to it ($request) and then perform the closure (or controller) function that the route is configured to perform, which includes:

  1. The Router::findRoute($request) method is used to query the route matching the request URI ($request) from the RouteCollection RouteCollection.
  2. Run the closure (or controller method) configured in the Route configuration phase, which is done in the Router::runRoute(Request $Request, Route $Route) method; 2.1 When running the route closure or controller method, the handle execution mode similar to HTTP kernel will be adopted to run the local middleware suitable for the current route; 2.2 Inside the final THEN method, $route->run() is used to run the route. $route (Illuminate\Routing\ route) is the route found by findRoute.
  3. Generate the HTTP response (done by the prepareResponse method).

Execute route closures or controllers

Finally, let’s dive into the Illuminate\Routing\Route source code to explore how the next Routing closure or controller is executed:

<? php namespace Illuminate\Routing; . class Route { /** * Run the route action and return the response. Runs the route closure or controller and returns the response. * * @ see https://github.com/laravel/framework/blob/5.5/src/Illuminate/Routing/Route.php#L163 / public function the run () { $this->container = $this->container ?: new Container; try { if ($this->isControllerAction()) { return $this->runController(); } return $this->runCallable(); } catch (HttpResponseException $e) { return $e->getResponse(); } } /** * Checks whether the route's action is a controller. Determines whether the route handler is a controller. * * @return bool */ protected function isControllerAction() { return is_string($this->action['uses']); } /** * Run the route action and return the response. Runs the closure route handler and returns the response. * * @return mixed */ protected function runCallable() { $callable = $this->action['uses']; return $callable(... array_values($this->resolveMethodDependencies( $this->parametersWithoutNulls(), new ReflectionFunction($this->action['uses']) ))); } /** * Run the route action and return the response. Run the controller route processing method and return the response result. * * @return mixed * * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException */ protected function RunController () {// Execute the (dispatch) controller method return in Illuminate\Routing\ControllerDispatcher $this->controllerDispatcher()->dispatch( $this, $this->getController(), $this->getControllerMethod() ); } /** * Get the controller instance for the route. Resolve the controller instance from the routing configuration. * * @return mixed */ public function getController() { if (! Router::get('/', 'HomeController@index'); Router::get('/', 'HomeController@index'); The **HomeController** controller instance is resolved from 'HomeController@index'. $class = $this->parseControllerCallback()[0]; $this->controller = $this->container->make(ltrim($class, '\\')); } return $this->controller; } /** * Get the controller method used for the route. Gets the controller method that the route needs to execute. * * @return string */ protected function getControllerMethod() {// Parse 'index' from 'HomeController@index'. return $this->parseControllerCallback()[1]; /** * Parse the controller. * * @return array */ protected function parseControllerCallback() { return Str::parseCallback($this->action['uses']); } /** * Get the dispatcher for the route's controller. * * @return \Illuminate\Routing\Contracts\ControllerDispatcher */  public function controllerDispatcher() { if ($this->container->bound(ControllerDispatcherContract::class)) { return $this->container->make(ControllerDispatcherContract::class); } // Illuminate\Routing\ControllerDispatcher return new ControllerDispatcher($this->container); }}Copy the code

Running controller method

<? php namespace Illuminate\Routing; use Illuminate\Container\Container; use Illuminate\Routing\Contracts\ControllerDispatcher as ControllerDispatcherContract; class ControllerDispatcher implements ControllerDispatcherContract { /** * Dispatch a request to a given controller and method. Distribute requests to a given controller and its methods. * * * @ @ see https://github.com/laravel/framework/blob/5.5/src/Illuminate/Routing/ControllerDispatcher.php#L38 param \Illuminate\Routing\Route $Route Route * @param mixed $controller Controller * @param string $method method * @return mixed */ 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)); }}Copy the code

added

In addition, to add some in the search for matching request route did not specify some operations, you can go to study:

  • Go to the Illuminate\Routing\RouteCollection collection (routes set by Router::get(‘/’, callback), etc.) and find out exactly how to match $request URIs to the route configuration.
  • This lookup involves the Illuminate\Support\Collection component, which is the partition method.

conclusion

In this article, we mainly learn some knowledge about routing processing:

  • How routes in Laravel are loaded into the project;
  • How to receive HTTP requests;
  • How to find the matched route according to the HTTP request ($request);
  • Run the route closure or controller method.

Hope to help you in learning Laravel.

The resources

Thanks for Laravel learning materials.

  • An in-depth analysis of the implementation principle of Laravel service provider
  • Laravel Facade system
  • Laravel Middleware principles
  • Laravel pipeline flow principle
  • In-depth understanding of Laravel middleware