laravel路由是什么

laravel中,路由是外界訪問Laravel應(yīng)用程序的通路,或者說路由定義了Laravel的應(yīng)用程序向外界提供服務(wù)的具體方式。路由會將用戶的請求按照事先規(guī)劃的方案提交給指定的控制器和方法來進(jìn)行處理。

laravel路由是什么

本教程操作環(huán)境:windows7系統(tǒng)、Laravel6版,DELL G3電腦。

路由是外界訪問Laravel應(yīng)用程序的通路或者說路由定義了Laravel的應(yīng)用程序向外界提供服務(wù)的具體方式:通過指定的URI、http請求方法以及路由參數(shù)(可選)才能正確訪問到路由定義的處理程序。

無論URI對應(yīng)的處理程序是一個簡單的閉包還是說是控制器方法沒有對應(yīng)的路由外界都訪問不到他們

今天我們就來看看Laravel是如何來設(shè)計和實現(xiàn)路由的。

我們在路由文件里通常是向下面這樣來定義路由的:

Route::get('/user',?'UsersController@index');

通過上面的路由我們可以知道,客戶端通過以HTTP GET方式來請求 URI “/user”時,Laravel會把請求最終派發(fā)給UsersController類的index方法來進(jìn)行處理,然后在index方法中返回響應(yīng)給客戶端。

上面注冊路由時用到的Route類在Laravel里叫門面(Facade),它提供了一種簡單的方式來訪問綁定到服務(wù)容器里的服務(wù)router,F(xiàn)acade的設(shè)計理念和實現(xiàn)方式我打算以后單開博文來寫,在這里我們只要知道調(diào)用的Route這個門面的靜態(tài)方法都對應(yīng)服務(wù)容器里router這個服務(wù)的方法,所以上面那條路由你也可以看成是這樣來注冊的:

app()->make('router')->get('user',?'UsersController@index');

router這個服務(wù)是在實例化應(yīng)用程序Application時在構(gòu)造方法里通過注冊RoutingServiceProvider時綁定到服務(wù)容器里的:

//bootstrap/app.php $app?=?new?IlluminateFoundationApplication( ????realpath(__DIR__.'/../') );  //Application:?構(gòu)造方法 public?function?__construct($basePath?=?null) { ????if?($basePath)?{ ????????$this->setBasePath($basePath); ????}  ????$this->registerBaseBindings();  ????$this->registerBaseServiceProviders();  ????$this->registerCoreContainerAliases(); }  //Application:?注冊基礎(chǔ)的服務(wù)提供器 protected?function?registerBaseServiceProviders() { ????$this->register(new?EventServiceProvider($this));  ????$this->register(new?LogServiceProvider($this));  ????$this->register(new?RoutingServiceProvider($this)); }  //IlluminateRoutingRoutingServiceProvider:?綁定router到服務(wù)容器 protected?function?registerRouter() { ????$this->app->singleton('router',?function?($app)?{ ????????return?new?Router($app['events'],?$app); ????}); }

通過上面的代碼我們知道了Route調(diào)用的靜態(tài)方法都對應(yīng)于IlluminateRoutingRouter類里的方法,Router這個類里包含了與路由的注冊、尋址、調(diào)度相關(guān)的方法。

下面我們從路由的注冊、加載、尋址這幾個階段來看一下laravel里是如何實現(xiàn)這些的。

路由加載

注冊路由前需要先加載路由文件,路由文件的加載是在AppProvidersRouteServiceProvider這個服務(wù)器提供者的boot方法里加載的:

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?IlluminateFoundationSupportProviders;  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 首先去尋找路由的緩存文件,沒有緩存文件再去進(jìn)行加載路由。緩存文件一般在 bootstrap/cache/routes.php 文件中。
方法loadRoutes會調(diào)用map方法來加載路由文件里的路由,map這個函數(shù)在AppProvidersRouteServiceProvider類中,這個類繼承自IlluminateFoundationSupportProvidersRouteServiceProvider。通過map方法我們能看到laravel將路由分為兩個大組:api、web。這兩個部分的路由分別寫在兩個文件中:routes/web.php、routes/api.php。

Laravel5.5里是把路由分別放在了幾個文件里,之前的版本是在app/Http/routes.php文件里。放在多個文件里能更方便地管理API路由和與WEB路由

路由注冊

我們通常都是用Route這個Facade調(diào)用靜態(tài)方法get, post, head, options, put, patch, delete……等來注冊路由,上面我們也說了這些靜態(tài)方法其實是調(diào)用了Router類里的方法:

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); } ....

可以看到路由的注冊統(tǒng)一都是由router類的addRoute方法來處理的:

//注冊路由到RouteCollection protected?function?addRoute($methods,?$uri,?$action) { ????return?$this->routes->add($this->createRoute($methods,?$uri,?$action)); }  //創(chuàng)建路由 protected?function?createRoute($methods,?$uri,?$action) { ????if?($this->actionReferencesController($action))?{ ????????//controller@action類型的路由在這里要進(jìn)行轉(zhuǎn)換 ????????$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; }

注冊路由時傳遞給addRoute的第三個參數(shù)action可以閉包、字符串或者數(shù)組,數(shù)組就是類似[‘uses’ => ‘Controller@action’, ‘middleware’ => ‘…’]這種形式的。如果action是Controller@action類型的路由將被轉(zhuǎn)換為action數(shù)組, convertToControllerAction執(zhí)行完后action的內(nèi)容為:

[ ????'uses'?=>?'AppHttpControllersSomeController@someAction', ????'controller'?=>?'AppHttpControllersSomeController@someAction' ]

可以看到把命名空間補(bǔ)充到了控制器的名稱前組成了完整的控制器類名,action數(shù)組構(gòu)建完成接下里就是創(chuàng)建路由了,創(chuàng)建路由即用指定的HTTP請求方法、URI字符串和action數(shù)組來創(chuàng)建IlluminateRoutingRoute類的實例:

protected?function?newRoute($methods,?$uri,?$action) { ????return?(new?Route($methods,?$uri,?$action)) ????????????????->setRouter($this) ????????????????->setContainer($this->container); }

路由創(chuàng)建完成后將Route添加到RouteCollection中去:

protected?function?addRoute($methods,?$uri,?$action) { ????return?$this->routes->add($this->createRoute($methods,?$uri,?$action)); }

router的$routes屬性就是一個RouteCollection對象,添加路由到RouteCollection對象時會更新RouteCollection對象的routes、allRoutes、nameList和actionList屬性

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的數(shù)組值中方便查找 ????????????$this->nameList[$action['as']]?=?$route; ????????}  ????????if?(isset($action['controller']))?{ ????????????$this->addToActionList($action,?$route); ????????} ????}  }

RouteCollection的四個屬性

routes中存放了HTTP請求方法與路由對象的映射:

[ ????'GET'?=>?[ ????????$routeUri1?=>?$routeObj1 ????????... ????] ????... ]

allRoutes屬性里存放的內(nèi)容時將routes屬性里的二位數(shù)組編程一位數(shù)組后的內(nèi)容:

[ ????'GET'?.?$routeUri1?=>?$routeObj1 ????'GET'?.?$routeUri2?=>?$routeObj2 ????... ]

nameList是路由名稱與路由對象的一個映射表

[ ????$routeName1?=>?$routeObj1 ????... ]

actionList是路由控制器方法字符串與路由對象的映射表

[ ????'AppHttpControllersControllerOne@ActionOne'?=>?$routeObj1 ]

這樣就算注冊好路由了。

路由尋址

中間件的文章里我們說過HTTP請求在經(jīng)過Pipeline通道上的中間件的前置操作后到達(dá)目的地:

//IlluminateFoundationHttpKernel 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函數(shù)返回的閉包:

$destination?=?function?($request)?{ ????$this->app->instance('request',?$request); ????return?$this->router->dispatch($request); };

在閉包里調(diào)用了router的dispatch方法,路由尋址就發(fā)生在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; ????} ???? }

尋找路由的任務(wù)由 RouteCollection 負(fù)責(zé),這個函數(shù)負(fù)責(zé)匹配路由,并且把 request 的 url 參數(shù)綁定到路由中:

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里的路徑參數(shù)綁定賦值給路由(如果有的話) ????????????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請求方法與路由對象的映射。

然后依次調(diào)用這路由里路由對象的matches方法, matches方法, matches方法里會對HTTP請求對象進(jìn)行一些驗證,驗證對應(yīng)的Validator是:UriValidator、MethodValidator、SchemeValidator、HostValidator。
在驗證之前在$this->compileRoute()里會將路由的規(guī)則轉(zhuǎn)換成正則表達(dá)式

UriValidator主要是看請求對象的URI是否與路由的正則規(guī)則匹配能匹配上:

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驗證協(xié)議是否正確(http|https), HostValidator驗證域名, 如果路由中不設(shè)置host屬性,那么這個驗證不會進(jìn)行。

一旦某個路由通過了全部的認(rèn)證就將會被返回,接下來就要將請求對象URI里的路徑參數(shù)綁定賦值給路由參數(shù):

路由參數(shù)綁定

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; ????????}); ????} }

賦值路由參數(shù)完成后路由尋址的過程就結(jié)束了,結(jié)下來就該運行通過匹配路由中對應(yīng)的控制器方法返回響應(yīng)對象了。

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?EventsRouteMatched($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; ????//收集路由和控制器里應(yīng)用的中間件 ????????$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?IlluminateRouting; 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(); ????????} ????}  }

這里我們主要介紹路由相關(guān)的內(nèi)容,runRoute的過程通過上面的源碼可以看到其實也很復(fù)雜, 會收集路由和控制器里的中間件,將請求通過中間件過濾才會最終到達(dá)目的地路由,執(zhí)行目的路由地run()方法,里面會判斷路由對應(yīng)的是一個控制器方法還是閉包然后進(jìn)行相應(yīng)地調(diào)用,最后把執(zhí)行結(jié)果包裝成Response對象返回給客戶端。這個過程還會涉及到我們以前介紹過的中間件過濾、服務(wù)解析、依賴注入方面的信息。

相關(guān)推薦:最新的五個Laravel視頻教程

以上就是

? 版權(quán)聲明
THE END
喜歡就支持一下吧
點贊13 分享