老王,你给我说说 Laravel 的请求是怎么到达控制器的
老王啊,你总在说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 协议》,转载必须注明作者和本文链接
mark一波
:+1: :+1: :+1:
看的都累了!
好文
:+1: :+1: :+1: :+1: :+1: :+1:
:thumbsup: :thumbsup: :thumbsup:
感觉像在水里别了一口气,一直在划水上浮,差点憋死在水里。还好终于等到了返回值了,我还呛了几口水...... :sweat_smile: