老王啊,你總在說Laravel的東西,能不能給我大概簡單的說一下一個請求是怎麼樣到達我寫在控制的程式碼中去的。中間都經歷了哪些東西啊。
入口
Laravel5.8 入口檔案為public/index.php
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
$response->send();
$kernel->terminate($request, $response);
建立了一個Kernel
物件,呼叫handler處理請求,獲取返回結果。將返回結果輸出到客戶端,處理terminate
操作。
Kernel中如何處理請求
容器裡繫結的是App\Http\Kernel
,繼承於Illuminate\Foundation\Http\Kernel
。
public function handle($request)
{
try {
$request->enableHttpMethodParameterOverride();
$response = $this->sendRequestThroughRouter($request);
} catch (Exception $e) {
$this->reportException($e);
$response = $this->renderException($request, $e);
} catch (Throwable $e) {
$this->reportException($e = new FatalThrowableError($e));
$response = $this->renderException($request, $e);
}
$this->app['events']->dispatch(
new Events\RequestHandled($request, $response)
);
return $response;
}
Kernel中呼叫sendRequestThroughRouter
方法,將請求傳遞到路由處理當中。
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());
}
在sendRequestThroughRouter
當中,在app中繫結了request
例項,並解綁掉其他request例項物件。這樣在程式其他地方都能透過app()->make('request')
獲取到request例項物件。
呼叫bootstrap
方法,載入引導類。
建立一個Pipeline
物件,將路由排程與中介軟體放入呼叫鏈當中。所有request先經過全域性的中介軟體
,然後在透過路由分發。
protected function dispatchToRouter()
{
return function ($request) {
$this->app->instance('request', $request);
return $this->router->dispatch($request);
};
}
因為Piepline
呼叫鏈都是一個個的回撥方法,所以在dispatchToRouter
返回了一個匿名回撥函式。使用Kernel
的route
屬性進行排程。
Kernel
的route
是一個Illuminate\Routing\Router
物件。
路由排程
//Illuminate\Routing\Router
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)
);
}
從上面的方法可以看出,最終透過findRoute
查詢當前匹配的路由物件,並呼叫runRoute
處理請求返回結果。
怎麼找到路由的
//Illuminate\Routing\Router
protected function findRoute($request)
{
$this->current = $route = $this->routes->match($request);
$this->container->instance(Route::class, $route);
return $route;
}
對路由的匹配,是透過routes
這個路由Collections
去匹配的。
//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;
}
protected function matchAgainstRoutes(array $routes, $request, $includingMethod = true)
{
[$fallbacks, $routes] = collect($routes)->partition(function ($route) {
return $route->isFallback;
});
return $routes->merge($fallbacks)->first(function ($value) use ($request, $includingMethod) {
return $value->matches($request, $includingMethod);
});
}
先透過請求的方法獲取當前方法下可用的路由集合,在從這些集合中去遍歷獲取第一個匹配的路由。集合中每個item
是一個Illuminate\Routing\Router
物件。因此最終判斷路由與請求是否匹配呼叫的是Illuminate\Routing\Router
中的matches方法。
//Illuminate\Routing\Router
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;
}
public static function getValidators()
{
if (isset(static::$validators)) {
return static::$validators;
}
return static::$validators = [
new UriValidator, new MethodValidator,
new SchemeValidator, new HostValidator,
];
}
在Illuminate\Routing\Router
提供了四個預設的驗證器,當四個驗證器透過的時候才會匹配成功。四個驗證器分別是UriValidator
驗證訪問路徑,MethodValidator
驗證請求方法,SchemeValidator
驗證訪問協議,HostValidator
驗證域名。其中對uri
的驗證內部是使用正規表示式驗證。
路由排程怎麼處理請求
//Illuminate\Routing\Router
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()
);
});
}
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();
}
}
路由對請求的處理也是返回一個Pipeline
,先將請求透過中介軟體,然後在執行路由的run方法。在run
方法裡面判斷當前是執行控制器方法還是回撥方法,根據不同型別分開執行。
怎麼執行
protected function isControllerAction()
{
return is_string($this->action['uses']);
}
protected function runCallable()
{
$callable = $this->action['uses'];
return $callable(...array_values($this->resolveMethodDependencies(
$this->parametersWithoutNulls(), new ReflectionFunction($this->action['uses'])
)));
}
protected function runController()
{
return $this->controllerDispatcher()->dispatch(
$this, $this->getController(), $this->getControllerMethod()
);
}
透過當前路由的action
配置判斷是否是控制器或者回撥方法。從程式碼中可以看到,其實就是我們路由配置中的第二個引數對應到action['user']
。當我們第二引數是一個字串的時候則認為是控制器方法,將請求轉發到控制器裡去處理。否則執行回撥函式處理。
到這裡,我們的請求就真的到達了我們的控制器的方法中,開始執行我們寫的程式碼了。
本作品採用《CC 協議》,轉載必須註明作者和本文連結