In laravel, routing is the way for the outside world to access Laravel applications, or routing defines the specific way in which Laravel applications provide services to the outside world. Routing will submit the user's request to the specified controller and method for processing according to the pre-planned plan.
The operating environment of this tutorial: Windows 7 system, Laravel 6 version, DELL G3 computer.
Routing is the way for the outside world to access Laravel applications, or routing defines the specific way in which Laravel applications provide services to the outside world: correct access can be achieved through the specified URI, HTTP request method, and routing parameters (optional) to the handler defined by the route.
Whether the handler corresponding to the URI is a simple closure or the controller method has no corresponding route, the outside world cannot access them
Today we will take a look at how Laravel came about Design and implement routing.
We usually define the route as follows in the routing file:
Route::get('/user', 'UsersController@index');
Through the above routing, we can know that the client requests the URI through HTTP GET " /user", Laravel will eventually dispatch the request to the index method of the UsersController class for processing, and then return the response to the client in the index method.
The Route class used when registering routes above is called Facade in Laravel. It provides a simple way to access the service router bound to the service container. The design concept and implementation of Facade I plan to write a separate blog post about the method in the future. Here we only need to know that the static methods of the Route facade that are called correspond to the methods of the router service in the service container, so you can also think of the above route as registering like this:
app()->make('router')->get('user', 'UsersController@index');
The router service is bound to the service container by registering the RoutingServiceProvider in the constructor when instantiating the application Application:
//bootstrap/app.php $app = new Illuminate\Foundation\Application( realpath(__DIR__.'/../') ); //Application: 构造方法 public function __construct($basePath = null) { if ($basePath) { $this->setBasePath($basePath); } $this->registerBaseBindings(); $this->registerBaseServiceProviders(); $this->registerCoreContainerAliases(); } //Application: 注册基础的服务提供器 protected function registerBaseServiceProviders() { $this->register(new EventServiceProvider($this)); $this->register(new LogServiceProvider($this)); $this->register(new RoutingServiceProvider($this)); } //\Illuminate\Routing\RoutingServiceProvider: 绑定router到服务容器 protected function registerRouter() { $this->app->singleton('router', function ($app) { return new Router($app['events'], $app); }); }
Through the above code, we know the Route call The static methods all correspond to the methods in the \Illuminate\Routing\Router
class. The Router class contains methods related to routing registration, addressing, and scheduling.
Let’s take a look at how this is implemented in laravel from the stages of routing registration, loading, and addressing.
Route loading
You need to load the routing file before registering the route. The routing file is loaded in App\Providers\RouteServiceProvider
Loaded in the boot method of this 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::prefix('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 $this->app->getCachedRoutesPath(); }); } protected function loadRoutes() { if (method_exists($this, 'map')) { $this->app->call([$this, 'map']); } } } class Application extends Container implements ApplicationContract, HttpKernelInterface { public function routesAreCached() { return $this['files']->exists($this->getCachedRoutesPath()); } public function getCachedRoutesPath() { return $this->bootstrapPath().'/cache/routes.php'; } }
laravel first looks for the cache file of the route, and then loads the route if there is no cache file. The cache file is usually in the bootstrap/cache/routes.php file.
The loadRoutes method will call the map method to load the routes in the routing file. The map function is in the App\Providers\RouteServiceProvider
class. This class inherits from Illuminate\Foundation\Support\Providers\ RouteServiceProvider
. Through the map method, we can see that laravel divides routing into two large groups: api and web. The routes for these two parts are written in two files respectively: routes/web.php and routes/api.php.
In Laravel 5.5, routes are placed in several files. The previous version was in the app/Http/routes.php file. Putting it in multiple files can make it easier to manage API routing and WEB routing
Route registration
us Usually the Route facade is used to call static methods get, post, head, options, put, patch, delete...etc. to register routes. As we said above, these static methods actually call the Router class. Method:
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 of routes is handled by the addRoute method of the router class:
//注册路由到RouteCollection 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)) { //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; } 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 action passed to addRoute when registering a route can be closed , string or array, the array is similar to ['uses' => 'Controller@action', 'middleware' => '...']. If the action is a route of type Controller@action
, it will be converted into an action array. After convertToControllerAction is executed, the content of the action is:
[ '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. The complete controller class name is formed before. After the action array is constructed, the next step is to create the route. To create a route, use the specified HTTP request method, URI string and action array to create \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 attribute of the router is a RouteCollection object, which will be updated when adding a route to the RouteCollection object. The routes, allRoutes, nameList and actionList attributes of the RouteCollection object
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['as'])) { //如果时命名路由,将route对象映射到以路由名为key的数组值中方便查找 $this->nameList[$action['as']] = $route; } if (isset($action['controller'])) { $this->addToActionList($action, $route); } } }
The four attributes of RouteCollection
routes stores the mapping between HTTP request methods and routing objects:
[ 'GET' => [ $routeUri1 => $routeObj1 ... ] ... ]
allRoutes The content stored in the attribute is the content after programming the two-digit array in the routes attribute into a one-digit array:
[ 'GET' . $routeUri1 => $routeObj1 'GET' . $routeUri2 => $routeObj2 ... ]
nameList is a mapping table between route names and routing objects
[ $routeName1 => $routeObj1 ... ]
actionList is the route Mapping table of controller method string and routing object
[ 'App\Http\Controllers\ControllerOne@ActionOne' => $routeObj1 ]
In this way, the route is registered.
路由寻址
中间件的文章里我们说过HTTP请求在经过Pipeline通道上的中间件的前置操作后到达目的地:
//Illuminate\Foundation\Http\Kernel class 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); }; } }
上面代码可以看到Pipeline的destination就是dispatchToRouter函数返回的闭包:
$destination = function ($request) { $this->app->instance('request', $request); return $this->router->dispatch($request); };
在闭包里调用了router的dispatch方法,路由寻址就发生在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; } }
寻找路由的任务由 RouteCollection 负责,这个函数负责匹配路由,并且把 request 的 url 参数绑定到路由中:
class RouteCollection implements Countable, IteratorAggregate { public function match(Request $request) { $routes = $this->get($request->getMethod()); $route = $this->matchAgainstRoutes($routes, $request); if (! is_null($route)) { //找到匹配的路由后,将URI里的路径参数绑定赋值给路由(如果有的话) 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, $includingMethod); }); } } 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());
会先加载注册路由阶段在RouteCollection里生成的routes属性里的值,routes中存放了HTTP请求方法与路由对象的映射。
然后依次调用这堆路由里路由对象的matches方法, matches方法, matches方法里会对HTTP请求对象进行一些验证,验证对应的Validator是:UriValidator、MethodValidator、SchemeValidator、HostValidator。
在验证之前在$this->compileRoute()
里会将路由的规则转换成正则表达式。
UriValidator主要是看请求对象的URI是否与路由的正则规则匹配能匹配上:
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验证请求方法, SchemeValidator验证协议是否正确(http|https), HostValidator验证域名, 如果路由中不设置host属性,那么这个验证不会进行。
一旦某个路由通过了全部的认证就将会被返回,接下来就要将请求对象URI里的路径参数绑定赋值给路由参数:
路由参数绑定
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->bindPathParameters($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->getRegex(), '/'.$request->decodedPath(), $matches); return $this->matchToKeys(array_slice($matches, 1)); } protected function matchToKeys(array $matches) { if (empty($parameterNames = $this->route->parameterNames())) { return []; } $parameters = array_intersect_key($matches, array_flip($parameterNames)); return array_filter($parameters, function ($value) { return is_string($value) && strlen($value) > 0; }); } }
赋值路由参数完成后路由寻址的过程就结束了,结下来就该运行通过匹配路由中对应的控制器方法返回响应对象了。
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) ); } 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() ); }); } } namespace Illuminate\Routing; class 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(); } } }
这里我们主要介绍路由相关的内容,runRoute的过程通过上面的源码可以看到其实也很复杂, 会收集路由和控制器里的中间件,将请求通过中间件过滤才会最终到达目的地路由,执行目的路由地run()
方法,里面会判断路由对应的是一个控制器方法还是闭包然后进行相应地调用,最后把执行结果包装成Response对象返回给客户端。这个过程还会涉及到我们以前介绍过的中间件过滤、服务解析、依赖注入方面的信息。
相关推荐:最新的五个Laravel视频教程
The above is the detailed content of what is laravel routing. For more information, please follow other related articles on the PHP Chinese website!