Routing is a way for outsiders to access laravel applications or routes that define how the Laravel application provides services to the outside world by specifying a URI, an HTTP request method, and, optionally, a routing parameter to properly access the handler for the route definition. Whether the handler for the URI is a simple closure or the Controller method does not have a corresponding route for the outside world to access them, today we'll look at how Laravel is designed and implemented for routing.
We usually define the route in the routing file as follows:
Route::get ('/user ', ' userscontroller@index ');
Through the above route we can know that when the client requests the URI "/user" by HTTP GET, Laravel sends the request to the Userscontroller class's index method for processing, and then returns the response to the client in the index method.
The route class used to register the route above is called façade (facade) in Laravel, which provides an easy way to access the design concept and implementation of the service Router,facade that is bound to the service container. I'm going to write Tankaboven later, Here we just need to know that the calling route of the façade of the static method is corresponding to the service container router The method of the service, so the above route you can also be seen as such to register:
App ()->make (' router ')->get (' User ', ' userscontroller@index ');
Router This service is bound to the service container when the application application is instantiated by registering Routingserviceprovider in the constructor method:
Bootstrap/app.php$app = new Illuminate\foundation\application ( realpath (__dir__. ') /.. /'));//application: Construction method Public Function __construct ($basePath = null) { if ($basePath) { $this->setbasepath ($basePath); } $this->registerbasebindings (); $this->registerbaseserviceproviders (); $this->registercorecontaineraliases ();} Application: Register the underlying service provider protected function Registerbaseserviceproviders () { $this->register (new Eventserviceprovider ($this)); $this->register (New Logserviceprovider ($this)); $this->register (New Routingserviceprovider ($this));} \illuminate\routing\routingserviceprovider: Bind router to service container protected function Registerrouter () { $this App->singleton (' Router ', function ($app) { return new router ($app [' Events '], $app); }
We know from the above code that the static method of the route call corresponds to the \Illuminate\Routing\Router
method in the class, and the router class contains methods related to the registration, addressing, and scheduling of the route.
Let's take a look at how these are implemented in Laravel from the stages of the registration, loading, and addressing of routes.
Route loading
The routing file needs to be loaded before the route is registered, and the loading of the route file is contained in the App\Providers\RouteServiceProvider
boot method of the server provider:
Class Routeserviceprovider extends serviceprovider{public function boot () { parent::boot (); } Public function Map () { $this->mapapiroutes (); $this->mapwebroutes (); } protected function mapwebroutes () { route::middleware (' web ') ->namespace ($this->namespace) ->group (Base_path (' routes/web.php ')); } protected function mapapiroutes () { Route::p refix (' API ') ->middleware (' API ') Namespace ($this->namespace) ->group (Base_path (' routes/api.php '));} }
namespace Illuminate\foundation\support\providers;class Routeserviceprovider extends serviceprovider{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 (); }); }} protected function Loadcachedroutes () {$this->app->booted (function () {Require $thi S->app->getcachedroutespath (); }); } protected function Loadroutes () {if (Method_exists ($this, ' map ')) {$this->app->call ([$th IS, ' map '); }}}class Application extends Container implements Applicationcontract, httpkernelinterface{public function Routesa RecacHed () {return $this [' Files ']->exists ($this->getcachedroutespath ()); Public Function Getcachedroutespath () {return $this->bootstrappath (). ' /cache/routes.php '; }}
Laravel first looks for the cached file for the route, without caching the file and then loading the route. The cache file is typically in the bootstrap/cache/routes.php file.
The Loadroutes method calls the map method to load the route in the route file, and the map function is in the App\Providers\RouteServiceProvider
class, and the class inherits from it Illuminate\Foundation\Support\Providers\RouteServiceProvider
. With the map method we can see that laravel divides the route into two large groups: API, Web. The two-part routes were written in two files: routes/web.php, routes/api.php.
In Laravel5.5, the routes were placed in several files, and the previous version was in the app/http/routes.php file. More convenient to manage API Routing and Web routing in multiple files
Route Registration
We usually use route this facade call static method Get, post, head, options, put, Patch, delete ... To register the route, we also say that these static methods actually call the method in the Router class:
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);} ....
You can see that the registration unification of a route is handled by the Addroute method of the Router class:
Register route to routecollectionprotected function Addroute ($methods, $uri, $action) {return $this->routes->add ($this- >createroute ($methods, $uri, $action));} Create route protected function Createroute ($methods, $uri, $action) {if ($this->actionreferencescontroller ($action)) { Controller@action type of route is to be converted here $action = $this->converttocontrolleraction ($action); } $route = $this->newroute ($methods, $this->prefix ($uri), $action); if ($this->hasgroupstack ()) {$this->mergegroupattributesintoroute ($route); } $this->addwhereclausestoroute ($route); return $route;} protected function Converttocontrolleraction ($action) {if (is_string ($action)) {$action = [' uses ' = = $action ]; if (! empty ($this->groupstack)) {$action [' uses '] = $this->prependgroupnamespace ($action [' uses '] ); } $action [' controller '] = $action [' uses ']; return $action;}
The third parameter that is passed to Addroute when you register a route action can be a closure, a string, or an array, which is similar to [' uses ' = ' = ' controller@action ', ' middleware ' = ' ... '] in this form. If the action is a Controller@action
type of route that will be converted to an action array, the contents of the action after execution of Converttocontrolleraction are:
[ ' uses ' = ' app\http\controllers\somecontroller@someaction ', ' controller ' = ' app\http\controllers\ ' Somecontroller@someaction ']
You can see that the namespace is added to the name of the controller to make up the full controller class name, the action array is built in the next is to create a route, create a route with the specified HTTP request method, URI string and action array to create an \Illuminate\Routing\Route
instance of the class:
protected function Newroute ($methods, $uri, $action) { return (new Route ($methods, $uri, $action)) Setrouter ($this) ->setcontainer ($this->container);}
After the route is created, add the route to the RouteCollection:
protected function Addroute ($methods, $uri, $action) { return $this->routes->add ($this->createroute ($ Methods, $uri, $action));}
The $routes property of router is a RouteCollection object that updates routecollection, routes, and Allroutes objects when routed to RouteCollection objects. NameList and ActionList Properties
Class RouteCollection implements countable, iteratoraggregate{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) { $action = $route->getaction (); if (Isset ($action [')]) { //If you name the route, map the route object to an array value with the route name key to easily find $this->namelist[$action [' as ']] = $ Route; } if (Isset ($action [' Controller ')]) { $this->addtoactionlist ($action, $route);}}}
Four properties of RouteCollection
The mapping of the HTTP request method to the route object is stored in the routes:
[ ' GET ' = [ $routeUri 1 = $routeObj 1 ... ] ...]
When the contents of the Allroutes attribute are stored, a two-bit array in the routes attribute is programmed with the contents of an array:
[ ' Get '. $routeUri 1 = $routeObj 1 ' get '. $routeUri 2 = $routeObj 2 ...]
NameList is a mapping table for route names and routing objects
[ $routeName 1 = $routeObj 1 ... ]
ActionList is a mapping table of route controller method strings and routing objects
[ ' app\http\controllers\controllerone@actionone ' = $routeObj 1]
This way, even if the route is registered.
Routing addressing
In the middleware article we said that the HTTP request arrived at the destination after the pre-operation of the middleware on the pipeline channel:
Illuminate\foundation\http\kernelclass Kernel implements kernelcontract{ 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 ()); } protected function Dispatchtorouter () { return function ($request) { $this->app->instance (' Request ', $request); return $this->router->dispatch ($request);}}
The above code can see that pipeline's destination is the closure that the Dispatchtorouter function returns:
$destination = function ($request) { $this->app->instance (' request ', $request); return $this->router->dispatch ($request);};
The dispatch method of router is called in the closure, and routing addressing occurs in the first stage of the dispatch Findroute:
Class Router implements Registrarcontract, bindingregistrar{public function Dispatch (Request $request) { $this->currentrequest = $request; return $this->dispatchtoroute ($request); } Public Function Dispatchtoroute (Request $request) { return $this->runroute ($request, $this->findroute ($request)); } protected function Findroute ($request) { $this->current = $route = $this->routes->match ($request) ; $this->container->instance (Route::class, $route); return $route; } }
The task of finding a route is the responsibility of routecollection, which is responsible for matching the route and binding the URL parameter of the request to the route:
Class RouteCollection implements countable, iteratoraggregate{public function match (Request $request) {$rou TES = $this->get ($request->getmethod ()); $route = $this->matchagainstroutes ($routes, $request); if (! Is_null ($route)) {return $route->bind ($request); } $others = $this->checkforalternateverbs ($request); if (count ($others) > 0) {return $this->getrouteformethods ($request, $others); } throw new Notfoundhttpexception; } protected function Matchagainstroutes (array $routes, $request, $includingMethod = True) {return Arr::first ($routes, function ($value) use ($request, $includingMethod) {return $value->matches ($request, $includingMe Thod); }); }}class route{Public Function matches (Request $request, $includingMethod = True) {$this->compileroute (); foreach ($this->getvalidators () as $validator) {if (!$includingMethod && $validator instanceof methodvalidator) {continue; } if (! $validator->matches ($this, $request)) {return false; }} return true; }}
$routes = $this->get($request->getMethod());
The value of the routes attribute generated in the routecollection in the registration routing phase is loaded first, and the mapping of the HTTP request method to the routing object is stored in routes.
Then call the matches method of the routing object in this heap route, matches method, the matches method will do some verification to the HTTP request object, verify the corresponding validator is: Urivalidator, Methodvalidator, Schemevalidator, Hostvalidator.
The $this->compileRoute()
rules for routing are converted to regular expressions before validation.
Urivalidator is mainly to see whether the URI of the request object matches the regular rule matching of the route:
Class Urivalidator implements validatorinterface{public function matches (Route $route, Request $request) { $path = $request->path () = = '/'? '/': '/'. $request->path (); Return Preg_match ($route->getcompiled ()->getregex (), Rawurldecode ($path));} }
Methodvalidator verify the Request method, Schemevalidator verify that the protocol is correct (HTTP|HTTPS), hostvalidator verify the domain name, and if the host property is not set in the route, this validation will not take place.
Once a route has passed all the authentication will be returned, the next step is to copy the path parameter bindings in the request object URI to the route parameters:
Routing parameter Bindings
Class route{Public Function bind (Request $request) {$this->compileroute (); $this->parameters = (new Routeparameterbinder ($this))->parameters ($request); return $this; }}class routeparameterbinder{Public Function parameters ($request) {$parameters = $this->bindpathparamete RS ($request); if (! Is_null ($this->route->compiled->gethostregex ())) {$parameters = $this->bindhostparameters ( $request, $parameters); } return $this->replacedefaults ($parameters); } protected function Bindpathparameters ($request) {Preg_match ($this->route->compiled->getre Gex (), '/'. $request->decodedpath (), $matches); return $this->matchtokeys (Array_slice ($matches, 1)); } protected function Matchtokeys (array $matches) {if (empty ($parameterNames = $this->route->parame Ternames ())) { return []; } $parameters = Array_intersect_key ($matches, Array_flip ($parameterNames)); Return Array_filter ($parameters, function ($value) {return is_string ($value) && strlen ($value) > 0; }); }}
The process of routing addressing after the completion of the assignment routing parameter is finished, and the runtime returns the response object by matching the corresponding controller method in the route.
Class Router implements Registrarcontract, bindingregistrar{public function Dispatch (Request $request) { $this->currentrequest = $request; return $this->dispatchtoroute ($request); } Public Function Dispatchtoroute (Request $request) { return $this->runroute ($request, $this->findroute ($request)); } 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) ); } }
Here we mainly introduce the content of the route, in the Runroute process through the above source can be seen in fact is also very complex, will collect the routing and Controller Middleware, the request through the middleware filtering will eventually call the Controller method to generate the response object, This process will also be designed to our previous introduction of the middleware filtering, service analysis, Dependency injection of information, if you look at the source is not understand where you can look at my previous articles.
Dependency Injection
Service container
Middleware
Related recommendations:
How to implement a fixed number of parameters in a laravel route
Laravel Routing settings and sub-route Settings example Analysis _php
Laravel Routing problems