一個請求如何跨過山和大海來到控制器的地盤。php
這塊代碼是在 Application
的構造函數中加載的web
public function __construct($basePath = null)
{
...
$this->registerBaseServiceProviders();
...
}
複製代碼
protected function registerBaseServiceProviders()
{
...
$this->register(new RoutingServiceProvider($this));
...
}
複製代碼
展開完整的服務提供者bootstrap
<?php
namespace 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);
});
}
}
複製代碼
後面在使用中會涉及這裏註冊的對象,紅框內就是註冊的綁定關係。api
啓動在這裏並無完成,這僅僅是啓動系統的基礎路由,在 app.php
中還有一個路由服務提供者 RouteServiceProvider
數組
<?php
namespace 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() 方法是在服務提供者全部 register() 方法執行完成以後在統一執行的"
// "這段代碼最後會調用 $this->map();"
parent::boot();
}
public function map()
{
$this->mapApiRoutes();
$this->mapWebRoutes();
}
// "這一塊的邏輯很是複雜就不展開了,主要功能就是優先加載 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'));
}
}
複製代碼
註冊完成以後就是開始處理,是從內核的 handle()
方法開始處理請求緩存
protected function sendRequestThroughRouter($request)
{
...
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
複製代碼
這段代碼在 【Laravel-海賊王系列】第七章,Pipeline 類解析 解析過了 不瞭解執行邏輯請先看上一篇哦~bash
這裏會在運行完中間件以後最後運行 $this->dispatchToRouter()
這個方法。session
$this->router
對象是在內核的構造函數注入的\Illuminate\Routing\Router
對象閉包
protected function dispatchToRouter()
{
return function ($request) {
$this->app->instance('request', $request);
return $this->router->dispatch($request);
};
}
複製代碼
那麼咱們接着看 dispatch
方法app
public function dispatch(Request $request)
{
$this->currentRequest = $request;
return $this->dispatchToRoute($request);
}
複製代碼
轉發一個請求給路由返回一個響應對象
public function dispatchToRoute(Request $request)
{
return $this->runRoute($request, $this->findRoute($request));
}
複製代碼
個人理解:router
表明路由器,route
則是表明一次路由的對象,
全部路由器的功能就是執行,派發路由對象。因此咱們須要先經過請求來拿到一個路由對象
protected function findRoute($request)
{
$this->current = $route = $this->routes->match($request);
// "綁定最新的 $route 對象到容器"
$this->container->instance(Route::class, $route);
// "返回路由"
return $route;
}
複製代碼
繼續分析 $this->routes->match($request);
,
這裏的 $this->routes
是構造函數注入的 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;
}
複製代碼
$routes
對象這裏面的值來自與路由緩存文件或者路由文件解析結果
繼續看 $route = $this->matchAgainstRoutes($routes, $request);
執行結果從請求中匹配對應路由並返回
若是沒有匹配的路由則使用請求方法之外的方法繼續匹配
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;
}
複製代碼
執行完成返回 $other
數組,若是仍是沒有則拋出throw new NotFoundHttpException;
這裏不詳細敘述了,若是匹配成功咱們將獲得一個 Illuminate\Routing\Route
對象傳遞下去。
當咱們獲得路由對象以後就是派發它了,根據給定的路由返回響應對象
protected function runRoute(Request $request, Route $route)
{
// "將這個閉包設置到 request 對象的 $this->routeResolver 成員上"
$request->setRouteResolver(function () use ($route) {
return $route;
});
// "執行路由匹配的事件,框架剛啓動的時候這裏什麼都不作"
$this->events->dispatch(new Events\RouteMatched($route, $request));
return $this->prepareResponse($request,
$this->runRouteWithinStack($route, $request)
);
}
複製代碼
執行到這裏就已經到了最後的部分了
return $this->prepareResponse($request,
$this->runRouteWithinStack($route, $request)
);
複製代碼
這個方法就是將 $request
和 $response
根據裏面的屬性封裝好數據返回而已。
public function prepareResponse($request, $response)
{
return static::toResponse($request, $response);
}
複製代碼
重點看 $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()
);
});
}
複製代碼
又到了這種用法,不理解執行邏輯請看第七章,
根據 Pipeline
的使用原理,咱們在經過全部 $middleware
以後會將 $requeset
傳遞給閉包來結束
因此這是終點!
function ($request) use ($route) {
return $this->prepareResponse(
$request, $route->run()
);
}
複製代碼
剛纔說過了 $this->prepareResponse()
這個方法沒什麼亮點就是
將請求和響應對象封裝返回,全部咱們應該知道了,$route->run() 將返回 response
對象!
來吧,經歷了無數使人髮指的封裝但願後面一片坦途,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();
}
}
複製代碼
總算看到了 runController()
方法了,想必路由跨過山和大海最總的歸宿也到這兒了
$this->isControllerAction()
是判斷路由是閉包仍是字符串
若是是字符串向上圖紅框中的內容則執行
protected function runController()
{
return $this->controllerDispatcher()->dispatch(
$this, $this->getController(), $this->getControllerMethod()
);
}
複製代碼
這裏是調用 Illuminate\Routing\ControllerDispatcher
的 dispatch
方法
public function dispatch(Route $route, $controller, $method)
{
// "從容器獲取當前類構造函數依賴和方法依賴參數"
$parameters = $this->resolveClassMethodDependencies(
$route->parametersWithoutNulls(), $controller, $method
);
if (method_exists($controller, 'callAction')) {
return $controller->callAction($method, $parameters);
}
return $controller->{$method}(...array_values($parameters));
}
複製代碼
callAction
來自全部控制器基礎的 Illuminate\Routing\Controller
public function callAction($method, $parameters)
{
return call_user_func_array([$this, $method], $parameters);
}
複製代碼
沒什麼好講的其實就是調用控制器對應的方法。
若是路由是閉包形式,則直接抽取路由對象中的閉包進行調用
protected function runCallable()
{
$callable = $this->action['uses'];
// "經過容器抽取依賴的參數傳入閉包運行"
return $callable(...array_values($this->resolveMethodDependencies(
$this->parametersWithoutNulls(), new ReflectionFunction($this->action['uses'])
)));
}
複製代碼
總算結束了,Laravel
路由在啓動階段註冊了很是多的類,
1.Application
構造階段 $this->register(new RoutingServiceProvider($this));
2.Kernel handle() bootstrap()
階段加載服務提供者的時候包含了 App\Providers\RouteServiceProvider::class,
這兩個階段註冊加上加載的邏輯是很是複雜,可是目的也很簡單從就是從路由文件轉成路由對象的過程,沒有力氣分析進去。
其餘的就是最後一直調用到控制器的過程,其中最後的 resolveClassMethodDependencies
, resolveMethodDependencies
也是很是值得研究的代碼。