【Laravel-海贼王系列】第十三章,路由控制器解析
路由
一個(gè)請求如何跨過山和大海來到控制器的地盤。
注冊路由
這塊代碼是在 Application 的構(gòu)造函數(shù)中加載的
public function __construct($basePath = null) {...$this->registerBaseServiceProviders();... } 復(fù)制代碼protected function registerBaseServiceProviders() {...$this->register(new RoutingServiceProvider($this));... } 復(fù)制代碼展開完整的服務(wù)提供者
<?phpnamespace Illuminate\Routing;use Illuminate\Support\ServiceProvider; use Psr\Http\Message\ResponseInterface; use Zend\Diactoros\Response as PsrResponse; use Psr\Http\Message\ServerRequestInterface; use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory; use Illuminate\Contracts\View\Factory as ViewFactoryContract; use Illuminate\Contracts\Routing\ResponseFactory as ResponseFactoryContract; use Illuminate\Routing\Contracts\ControllerDispatcher as ControllerDispatcherContract;class RoutingServiceProvider extends ServiceProvider {public function register(){$this->registerRouter();$this->registerUrlGenerator();$this->registerRedirector();$this->registerPsrRequest();$this->registerPsrResponse();$this->registerResponseFactory();$this->registerControllerDispatcher();}protected function registerRouter(){$this->app->singleton('router', function ($app) {return new Router($app['events'], $app);});}protected function registerUrlGenerator(){$this->app->singleton('url', function ($app) {$routes = $app['router']->getRoutes();$app->instance('routes', $routes);$url = new UrlGenerator($routes, $app->rebinding('request', $this->requestRebinder()), $app['config']['app.asset_url']);$url->setSessionResolver(function () {return $this->app['session'];});$url->setKeyResolver(function () {return $this->app->make('config')->get('app.key');});$app->rebinding('routes', function ($app, $routes) {$app['url']->setRoutes($routes);});return $url;});}protected function requestRebinder(){return function ($app, $request) {$app['url']->setRequest($request);};}protected function registerRedirector(){$this->app->singleton('redirect', function ($app) {$redirector = new Redirector($app['url']);if (isset($app['session.store'])) {$redirector->setSession($app['session.store']);}return $redirector;});}protected function registerPsrRequest(){$this->app->bind(ServerRequestInterface::class, function ($app) {return (new DiactorosFactory)->createRequest($app->make('request'));});}protected function registerPsrResponse(){$this->app->bind(ResponseInterface::class, function () {return new PsrResponse;});}protected function registerResponseFactory(){$this->app->singleton(ResponseFactoryContract::class, function ($app) {return new ResponseFactory($app[ViewFactoryContract::class], $app['redirect']);});}protected function registerControllerDispatcher(){$this->app->singleton(ControllerDispatcherContract::class, function ($app) {return new ControllerDispatcher($app);});} }復(fù)制代碼后面在使用中會(huì)涉及這里注冊的對象,紅框內(nèi)就是注冊的綁定關(guān)系。
啟動(dòng)在這里并沒有完成,這僅僅是啟動(dòng)系統(tǒng)的基礎(chǔ)路由,在 app.php 中還有一個(gè)路由服務(wù)提供者 RouteServiceProvider
<?phpnamespace App\Providers;use Illuminate\Support\Facades\Route; use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;class RouteServiceProvider extends ServiceProvider {protected $namespace = 'App\Http\Controllers';public function boot(){// boot() 方法是在服務(wù)提供者所有 register() 方法執(zhí)行完成之后在統(tǒng)一執(zhí)行的// 這段代碼最后會(huì)調(diào)用 $this->map();parent::boot();}public function map(){$this->mapApiRoutes();$this->mapWebRoutes();}// 這一塊的邏輯非常復(fù)雜就不展開了,主要功能就是優(yōu)先加載 cache/routes.php,如果不存在// 則從給定的路徑加載路由文件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'));} }復(fù)制代碼內(nèi)核啟動(dòng)
注冊完成之后就是開始處理,是從內(nèi)核的 handle() 方法開始處理請求
protected function sendRequestThroughRouter($request){...return (new Pipeline($this->app))->send($request)->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)->then($this->dispatchToRouter());} 復(fù)制代碼這段代碼在 【Laravel-海賊王系列】第七章,Pipeline 類解析 解析過了 不了解執(zhí)行邏輯請先看上一篇哦~
這里會(huì)在運(yùn)行完中間件之后最后運(yùn)行 $this->dispatchToRouter() 這個(gè)方法。
$this->router 對象是在內(nèi)核的構(gòu)造函數(shù)注入的 \Illuminate\Routing\Router 對象
protected function dispatchToRouter(){return function ($request) {$this->app->instance('request', $request);return $this->router->dispatch($request);};} 復(fù)制代碼那么我們接著看 dispatch 方法
public function dispatch(Request $request){$this->currentRequest = $request;return $this->dispatchToRoute($request);} 復(fù)制代碼轉(zhuǎn)發(fā)一個(gè)請求給路由返回一個(gè)響應(yīng)對象
public function dispatchToRoute(Request $request){return $this->runRoute($request, $this->findRoute($request));} 復(fù)制代碼找到路由
我的理解:router 代表路由器,route 則是代表一次路由的對象,
所有路由器的功能就是執(zhí)行,派發(fā)路由對象。所以我們需要先通過請求來拿到一個(gè)路由對象
protected function findRoute($request){$this->current = $route = $this->routes->match($request);// 綁定最新的 $route 對象到容器$this->container->instance(Route::class, $route);// 返回路由return $route;} 復(fù)制代碼繼續(xù)分析 $this->routes->match($request);,
這里的 $this->routes 是構(gòu)造函數(shù)注入的 Illuminate\Routing\RouteCollection 對象
public function match(Request $request){$routes = $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;} 復(fù)制代碼$routes 對象這里面的值來自與路由緩存文件或者路由文件解析結(jié)果
繼續(xù)看 $route = $this->matchAgainstRoutes($routes, $request); 執(zhí)行結(jié)果從請求中匹配對應(yīng)路由并返回
如果沒有匹配的路由則使用請求方法以外的方法繼續(xù)匹配
public static $verbs = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'];protected function checkForAlternateVerbs($request){$methods = array_diff(Router::$verbs, [$request->getMethod()]);$others = [];foreach ($methods as $method) {if (! is_null($this->matchAgainstRoutes($this->get($method), $request, false))) {$others[] = $method;}}return $others;} 復(fù)制代碼執(zhí)行完成返回 $other 數(shù)組,如果還是沒有則拋出throw new NotFoundHttpException;
這里不詳細(xì)敘述了,如果匹配成功我們將得到一個(gè) Illuminate\Routing\Route 對象傳遞下去。
派發(fā)路由
當(dāng)我們得到路由對象之后就是派發(fā)它了,根據(jù)給定的路由返回響應(yīng)對象
protected function runRoute(Request $request, Route $route){// 將這個(gè)閉包設(shè)置到 request 對象的 $this->routeResolver 成員上$request->setRouteResolver(function () use ($route) {return $route;});// 執(zhí)行路由匹配的事件,框架剛啟動(dòng)的時(shí)候這里什么都不做$this->events->dispatch(new Events\RouteMatched($route, $request));return $this->prepareResponse($request,$this->runRouteWithinStack($route, $request));} 復(fù)制代碼獲取響應(yīng)
執(zhí)行到這里就已經(jīng)到了最后的部分了
return $this->prepareResponse($request,$this->runRouteWithinStack($route, $request)); 復(fù)制代碼這個(gè)方法就是將 $request 和 $response 根據(jù)里面的屬性封裝好數(shù)據(jù)返回而已。
public function prepareResponse($request, $response){return static::toResponse($request, $response);} 復(fù)制代碼重點(diǎn)看 $this->runRouteWithinStack($route, $request) 這段話才是將請求傳遞到控制器關(guān)鍵!
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());});} 復(fù)制代碼又到了這種用法,不理解執(zhí)行邏輯請看第七章,
根據(jù) Pipeline 的使用原理,我們在通過所有 $middleware
之后會(huì)將 $requeset 傳遞給閉包來結(jié)束
所以這是終點(diǎn)!
function ($request) use ($route) {return $this->prepareResponse($request, $route->run());} 復(fù)制代碼剛才說過了 $this->prepareResponse() 這個(gè)方法沒什么亮點(diǎn)就是
將請求和響應(yīng)對象封裝返回,所有我們應(yīng)該知道了,$route->run() 將返回 response 對象!
控制器閃亮登場
來吧,經(jīng)歷了無數(shù)令人發(fā)指的封裝希望后面一片坦途,run()
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();}} 復(fù)制代碼總算看到了 runController() 方法了,想必路由跨過山和大海最總的歸宿也到這兒了
$this->isControllerAction() 是判斷路由是閉包還是字符串
如果是字符串向上圖紅框中的內(nèi)容則執(zhí)行
protected function runController(){return $this->controllerDispatcher()->dispatch($this, $this->getController(), $this->getControllerMethod());} 復(fù)制代碼這里是調(diào)用 Illuminate\Routing\ControllerDispatcher 的 dispatch 方法
public function dispatch(Route $route, $controller, $method){$parameters = $this->resolveClassMethodDependencies($route->parametersWithoutNulls(), $controller, $method); // 從容器獲取當(dāng)前類構(gòu)造函數(shù)依賴和方法依賴參數(shù)if (method_exists($controller, 'callAction')) {return $controller->callAction($method, $parameters);}return $controller->{$method}(...array_values($parameters));} 復(fù)制代碼callAction 來自所有控制器基礎(chǔ)的 Illuminate\Routing\Controller
public function callAction($method, $parameters){return call_user_func_array([$this, $method], $parameters);} 復(fù)制代碼沒什么好講的其實(shí)就是調(diào)用控制器對應(yīng)的方法。
如果路由是閉包形式,則直接抽取路由對象中的閉包進(jìn)行調(diào)用
protected function runCallable(){$callable = $this->action['uses'];// 通過容器抽取依賴的參數(shù)傳入閉包運(yùn)行return $callable(...array_values($this->resolveMethodDependencies($this->parametersWithoutNulls(), new ReflectionFunction($this->action['uses']))));} 復(fù)制代碼結(jié)語
總算結(jié)束了,Laravel 路由在啟動(dòng)階段注冊了非常多的類,
1.Application 構(gòu)造階段 $this->register(new RoutingServiceProvider($this));
2.Kernel handle() bootstrap() 階段加載服務(wù)提供者的時(shí)候包含了 App\Providers\RouteServiceProvider::class,
這兩個(gè)階段注冊加上加載的邏輯是非常復(fù)雜,但是目的也很簡單從就是從路由文件轉(zhuǎn)成路由對象的過程,沒有力氣分析進(jìn)去。
其他的就是最后一直調(diào)用到控制器的過程,其中最后的 resolveClassMethodDependencies , resolveMethodDependencies
也是非常值得研究的代碼。
總結(jié)
以上是生活随笔為你收集整理的【Laravel-海贼王系列】第十三章,路由控制器解析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring Cloud微服务系列文,服
- 下一篇: JS 中的装饰器