再读 Larave 核心源码
像所有web框架一样,Laravel的核心也是一个读取HTTP请求,返回应答内容的类:Kernel,该类继承自Symfony的Kernel类;
在Kernel生成Response的过程的各个阶段都会有各种事件Event触发,用来控制整个请求的生命周期,类似于钩子机制。
Kernel的入口handle方法
/**
* Handle an incoming HTTP request.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
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;
}
主要逻辑部分 sendRequestThroughRouter
/**
* Send the given request through the middleware / router.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
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());
}
//...
/**
* Get the route dispatcher callback.
*
* @return \Closure
*/
protected function dispatchToRouter()
{
return function ($request) {
$this->app->instance('request', $request);
return $this->router->dispatch($request);
};
}
接下来到Router类
/**
* Dispatch the request to the application.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse
*/
public function dispatch(Request $request)
{
$this->currentRequest = $request;
return $this->dispatchToRoute($request);
}
/**
* Dispatch the request to a route and return the response.
*
* @param \Illuminate\Http\Request $request
* @return mixed
*/
public function dispatchToRoute(Request $request)
{
return $this->runRoute($request, $this->findRoute($request));
}
/**
* Find the route matching a given request.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Routing\Route
*/
protected function findRoute($request)
{
$this->current = $route = $this->routes->match($request);
$this->container->instance(Route::class, $route);
return $route;
}
/**
* Return the response for the given route.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Routing\Route $route
* @return mixed
*/
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)
);
}
/**
* Run the given route within a Stack "onion" instance.
*
* @param \Illuminate\Routing\Route $route
* @param \Illuminate\Http\Request $request
* @return mixed
*/
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()
);
});
}
到这里,感觉好像又跟最初的Kernel::sendRequestThroughRouter 方法里面那个管道的用法非常类似了,只有最后一步then的参数不同。那么为什么要走两次中间件呢?前者Kernel里走的是通用中间件,而后者Router里面走的是特定路由Route的中间件,即分别为下面的middleware和middlewareGroups对应的中间件class。
protected $middleware = [
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\App\Http\Middleware\TrustProxies::class,
];
/**
* The application's route middleware groups.
*
* @var array
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'throttle:60,1',
'bindings',
],
];
最后 $route->run()方法,跳到路由类Route的run方法,开始真正走到执行控制器方法。
/**
* Run the route action and return the response.
*
* @return mixed
*/
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和runCallable(判断控制器方法还是回调函数逻辑很简单,路由配置的uses值为string就认为是控制器方法),接下来需要解决注入控制器方法或者匿名回调函数的参数依赖问题。
-
可以先从实现上比较简单的匿名回调函数的参数依赖看起。
/** * Run the route action and return the response. * * @return mixed */ protected function runCallable() { $callable = $this->action['uses']; return $callable(...array_values($this->resolveMethodDependencies( $this->parametersWithoutNulls(), new ReflectionFunction($this->action['uses']) ))); }
parametersWithoutNulls()返回Route类parameters属性过滤掉NULL值之后的数组,而parameters属性数组是在RouteCollection::match方法中通过Route::bind方法初始化的。 Router的$routes属性即RouteCollection的一个实例,在RoutingServiceProvider中通过Router的setter初始化。
这里的$this->parametersWithoutNulls()取自的$parameters仅仅是路由中定义的{}参数,并非query_string所有url参数,例如/user/{id} 访问/user/1?age=22,那么:
$parameters = ['id' => 1]
$route = $this->matchAgainstRoutes($routes, $request);
if (! is_null($route)) {
return $route->bind($request);
}
回到runCallable()的话题,resolveMethodDependencies是RouteDependencyResolverTrait的一个Trait方法
/**
* Resolve the given method's type-hinted dependencies.
*
* @param array $parameters
* @param \ReflectionFunctionAbstract $reflector
* @return array
*/
public function resolveMethodDependencies(array $parameters, ReflectionFunctionAbstract $reflector)
{
$instanceCount = 0;
$values = array_values($parameters);
foreach ($reflector->getParameters() as $key => $parameter) {
$instance = $this->transformDependency(
$parameter, $parameters
);
if (! is_null($instance)) {
$instanceCount++;
$this->spliceIntoParameters($parameters, $key, $instance);
} elseif (! isset($values[$key - $instanceCount]) &&
$parameter->isDefaultValueAvailable()) {
$this->spliceIntoParameters($parameters, $key, $parameter->getDefaultValue());
}
}
return $parameters;
}
/**
* Attempt to transform the given parameter into a class instance.
*
* @param \ReflectionParameter $parameter
* @param array $parameters
* @return mixed
*/
protected function transformDependency(ReflectionParameter $parameter, $parameters)
{
$class = $parameter->getClass();
// If the parameter has a type-hinted class, we will check to see if it is already in
// the list of parameters. If it is we will just skip it as it is probably a model
// binding and we do not want to mess with those; otherwise, we resolve it here.
if ($class && ! $this->alreadyInParameters($class->name, $parameters)) {
return $parameter->isDefaultValueAvailable()
? $parameter->getDefaultValue()
: $this->container->make($class->name);
}
}
代码注释里面解释了为什么type-hint的需要注入的参数,需要检查路由参数中是否已经存在:可能存在model-binding参数;
需要注意的是,回调函数(控制器方法)反射参数也是包含路由参数名的,且先后顺序一致,所以$this->spliceIntoParameters($parameters, $key, $instance);
才能把参数插入到正确的位置。
-
Route::runController
/** * Run the route action and return the response. * * @return mixed * * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException */ protected function runController() { return $this->controllerDispatcher()->dispatch( $this, $this->getController(), $this->getControllerMethod() ); }
实例化并调用了ControllerDispatcher::dispatch方法
/** * Dispatch a request to a given controller and method. * * @param \Illuminate\Routing\Route $route * @param mixed $controller * @param string $method * @return mixed */ 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($method, $parameters)方法,用来自定义对控制器方法进行修改。
Laravel的Kernel是核心流程,而容器+插件(ServiceProvider)实现了Laravel最为强大的功能,提供了它无与伦比的可扩展性。
本作品采用《CC 协议》,转载必须注明作者和本文链接
这个不错 翻译:Laravel 底层分析:生命周期和容器 Container(第一部分)