您好,登錄后才能下訂單哦!
這篇文章將為大家詳細(xì)講解有關(guān)laravel路由指的是什么,小編覺(jué)得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。
在laravel中,路由是外界訪問(wèn)Laravel應(yīng)用程序的通路,或者說(shuō)路由定義了Laravel的應(yīng)用程序向外界提供服務(wù)的具體方式。路由會(huì)將用戶的請(qǐng)求按照事先規(guī)劃的方案提交給指定的控制器和方法來(lái)進(jìn)行處理。
本教程操作環(huán)境:windows7系統(tǒng)、Laravel6版,DELL G3電腦。
路由是外界訪問(wèn)Laravel應(yīng)用程序的通路或者說(shuō)路由定義了Laravel的應(yīng)用程序向外界提供服務(wù)的具體方式:通過(guò)指定的URI、HTTP請(qǐng)求方法以及路由參數(shù)(可選)才能正確訪問(wèn)到路由定義的處理程序。
無(wú)論URI對(duì)應(yīng)的處理程序是一個(gè)簡(jiǎn)單的閉包還是說(shuō)是控制器方法沒(méi)有對(duì)應(yīng)的路由外界都訪問(wèn)不到他們
今天我們就來(lái)看看Laravel是如何來(lái)設(shè)計(jì)和實(shí)現(xiàn)路由的。
我們?cè)诼酚晌募锿ǔJ窍蛳旅孢@樣來(lái)定義路由的:
Route::get('/user', 'UsersController@index');
通過(guò)上面的路由我們可以知道,客戶端通過(guò)以HTTP GET方式來(lái)請(qǐng)求 URI "/user"時(shí),Laravel會(huì)把請(qǐng)求最終派發(fā)給UsersController類的index方法來(lái)進(jìn)行處理,然后在index方法中返回響應(yīng)給客戶端。
上面注冊(cè)路由時(shí)用到的Route類在Laravel里叫門(mén)面(Facade),它提供了一種簡(jiǎn)單的方式來(lái)訪問(wèn)綁定到服務(wù)容器里的服務(wù)router,F(xiàn)acade的設(shè)計(jì)理念和實(shí)現(xiàn)方式我打算以后單開(kāi)博文來(lái)寫(xiě),在這里我們只要知道調(diào)用的Route這個(gè)門(mén)面的靜態(tài)方法都對(duì)應(yīng)服務(wù)容器里router這個(gè)服務(wù)的方法,所以上面那條路由你也可以看成是這樣來(lái)注冊(cè)的:
app()->make('router')->get('user', 'UsersController@index');
router這個(gè)服務(wù)是在實(shí)例化應(yīng)用程序Application時(shí)在構(gòu)造方法里通過(guò)注冊(cè)RoutingServiceProvider時(shí)綁定到服務(wù)容器里的:
//bootstrap/app.php $app = new Illuminate\Foundation\Application( realpath(__DIR__.'/../') ); //Application: 構(gòu)造方法 public function __construct($basePath = null) { if ($basePath) { $this->setBasePath($basePath); } $this->registerBaseBindings(); $this->registerBaseServiceProviders(); $this->registerCoreContainerAliases(); } //Application: 注冊(cè)基礎(chǔ)的服務(wù)提供器 protected function registerBaseServiceProviders() { $this->register(new EventServiceProvider($this)); $this->register(new LogServiceProvider($this)); $this->register(new RoutingServiceProvider($this)); } //\Illuminate\Routing\RoutingServiceProvider: 綁定router到服務(wù)容器 protected function registerRouter() { $this->app->singleton('router', function ($app) { return new Router($app['events'], $app); }); }
通過(guò)上面的代碼我們知道了Route調(diào)用的靜態(tài)方法都對(duì)應(yīng)于\Illuminate\Routing\Router
類里的方法,Router這個(gè)類里包含了與路由的注冊(cè)、尋址、調(diào)度相關(guān)的方法。
下面我們從路由的注冊(cè)、加載、尋址這幾個(gè)階段來(lái)看一下laravel里是如何實(shí)現(xiàn)這些的。
路由加載
注冊(cè)路由前需要先加載路由文件,路由文件的加載是在App\Providers\RouteServiceProvider
這個(gè)服務(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 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 首先去尋找路由的緩存文件,沒(méi)有緩存文件再去進(jìn)行加載路由。緩存文件一般在 bootstrap/cache/routes.php 文件中。
方法loadRoutes會(huì)調(diào)用map方法來(lái)加載路由文件里的路由,map這個(gè)函數(shù)在App\Providers\RouteServiceProvider
類中,這個(gè)類繼承自Illuminate\Foundation\Support\Providers\RouteServiceProvider
。通過(guò)map方法我們能看到laravel將路由分為兩個(gè)大組:api、web。這兩個(gè)部分的路由分別寫(xiě)在兩個(gè)文件中:routes/web.php、routes/api.php。
Laravel5.5里是把路由分別放在了幾個(gè)文件里,之前的版本是在app/Http/routes.php文件里。放在多個(gè)文件里能更方便地管理API路由和與WEB路由
路由注冊(cè)
我們通常都是用Route這個(gè)Facade調(diào)用靜態(tài)方法get, post, head, options, put, patch, delete......等來(lái)注冊(cè)路由,上面我們也說(shuō)了這些靜態(tài)方法其實(shí)是調(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); } ....
可以看到路由的注冊(cè)統(tǒng)一都是由router類的addRoute方法來(lái)處理的:
//注冊(cè)路由到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; }
注冊(cè)路由時(shí)傳遞給addRoute的第三個(gè)參數(shù)action可以閉包、字符串或者數(shù)組,數(shù)組就是類似['uses' => 'Controller@action', 'middleware' => '...']這種形式的。如果action是Controller@action
類型的路由將被轉(zhuǎn)換為action數(shù)組, convertToControllerAction執(zhí)行完后action的內(nèi)容為:
[ 'uses' => 'App\Http\Controllers\SomeController@someAction', 'controller' => 'App\Http\Controllers\SomeController@someAction' ]
可以看到把命名空間補(bǔ)充到了控制器的名稱前組成了完整的控制器類名,action數(shù)組構(gòu)建完成接下里就是創(chuàng)建路由了,創(chuàng)建路由即用指定的HTTP請(qǐng)求方法、URI字符串和action數(shù)組來(lái)創(chuàng)建\Illuminate\Routing\Route
類的實(shí)例:
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屬性就是一個(gè)RouteCollection對(duì)象,添加路由到RouteCollection對(duì)象時(shí)會(huì)更新RouteCollection對(duì)象的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'])) { //如果時(shí)命名路由,將route對(duì)象映射到以路由名為key的數(shù)組值中方便查找 $this->nameList[$action['as']] = $route; } if (isset($action['controller'])) { $this->addToActionList($action, $route); } } }
RouteCollection的四個(gè)屬性
routes中存放了HTTP請(qǐng)求方法與路由對(duì)象的映射:
[ 'GET' => [ $routeUri1 => $routeObj1 ... ] ... ]
allRoutes屬性里存放的內(nèi)容時(shí)將routes屬性里的二位數(shù)組編程一位數(shù)組后的內(nèi)容:
[ 'GET' . $routeUri1 => $routeObj1 'GET' . $routeUri2 => $routeObj2 ... ]
nameList是路由名稱與路由對(duì)象的一個(gè)映射表
[ $routeName1 => $routeObj1 ... ]
actionList是路由控制器方法字符串與路由對(duì)象的映射表
[ 'App\Http\Controllers\ControllerOne@ActionOne' => $routeObj1 ]
這樣就算注冊(cè)好路由了。
路由尋址
中間件的文章里我們說(shuō)過(guò)HTTP請(qǐng)求在經(jīng)過(guò)Pipeline通道上的中間件的前置操作后到達(dá)目的地:
//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函數(shù)返回的閉包:
$destination = function ($request) { $this->app->instance('request', $request); return $this->router->dispatch($request); };
在閉包里調(diào)用了router的dispatch方法,路由尋址就發(fā)生在dispatch的第一個(gè)階段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é),這個(gè)函數(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());
會(huì)先加載注冊(cè)路由階段在RouteCollection里生成的routes屬性里的值,routes中存放了HTTP請(qǐng)求方法與路由對(duì)象的映射。
然后依次調(diào)用這堆路由里路由對(duì)象的matches方法, matches方法, matches方法里會(huì)對(duì)HTTP請(qǐng)求對(duì)象進(jìn)行一些驗(yàn)證,驗(yàn)證對(duì)應(yīng)的Validator是:UriValidator、MethodValidator、SchemeValidator、HostValidator。
在驗(yàn)證之前在$this->compileRoute()
里會(huì)將路由的規(guī)則轉(zhuǎn)換成正則表達(dá)式。
UriValidator主要是看請(qǐng)求對(duì)象的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驗(yàn)證請(qǐng)求方法, SchemeValidator驗(yàn)證協(xié)議是否正確(http|https), HostValidator驗(yàn)證域名, 如果路由中不設(shè)置host屬性,那么這個(gè)驗(yàn)證不會(huì)進(jìn)行。
一旦某個(gè)路由通過(guò)了全部的認(rèn)證就將會(huì)被返回,接下來(lái)就要將請(qǐng)求對(duì)象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ù)完成后路由尋址的過(guò)程就結(jié)束了,結(jié)下來(lái)就該運(yùn)行通過(guò)匹配路由中對(duì)應(yīng)的控制器方法返回響應(yīng)對(duì)象了。
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; //收集路由和控制器里應(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 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(); } } }
這里我們主要介紹路由相關(guān)的內(nèi)容,runRoute的過(guò)程通過(guò)上面的源碼可以看到其實(shí)也很復(fù)雜, 會(huì)收集路由和控制器里的中間件,將請(qǐng)求通過(guò)中間件過(guò)濾才會(huì)最終到達(dá)目的地路由,執(zhí)行目的路由地run()
方法,里面會(huì)判斷路由對(duì)應(yīng)的是一個(gè)控制器方法還是閉包然后進(jìn)行相應(yīng)地調(diào)用,最后把執(zhí)行結(jié)果包裝成Response對(duì)象返回給客戶端。這個(gè)過(guò)程還會(huì)涉及到我們以前介紹過(guò)的中間件過(guò)濾、服務(wù)解析、依賴注入方面的信息。
關(guān)于“l(fā)aravel路由指的是什么”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,使各位可以學(xué)到更多知識(shí),如果覺(jué)得文章不錯(cuò),請(qǐng)把它分享出去讓更多的人看到。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。