Laravel 源码学习笔记 4:kernel 实例解析 request 请求
上篇文章说到了从容器中解析出http的kernel实例,这篇文章讲kernel怎么处理请求。
在kernel中的handle
public function handle($request)
{
try {
//方法欺骗,csrf
$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方法
protected function sendRequestThroughRouter($request)
{
//1.绑定请求对象到容器
$this->app->instance('request', $request);
//清除之前的request
Facade::clearResolvedInstance('request');
//3.启动容器 ,分别启动里面的 env config 异常 注册facede 注册provider 引导provider
$this->bootstrap();
//3.请求通过管道中间件,分发匹配路由
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
这里做了三件事,如上图代码注释,启动容器这里要着重看下,点进bootstrap方法
public function bootstrap()
{
if (! $this->app->hasBeenBootstrapped()) {
$this->app->bootstrapWith($this->bootstrappers());
}
}
protected $bootstrappers = [
\Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,//设置env文件
\Illuminate\Foundation\Bootstrap\LoadConfiguration::class,//配置文件信息读取,主要为了config方法
\Illuminate\Foundation\Bootstrap\HandleExceptions::class,//异常错误级别的设置什么的
\Illuminate\Foundation\Bootstrap\RegisterFacades::class,//注册别名,没太懂
\Illuminate\Foundation\Bootstrap\RegisterProviders::class,//注册服务提供者
\Illuminate\Foundation\Bootstrap\BootProviders::class,//启动引导服务提供者
];
Application.php 中
public function bootstrapWith(array $bootstrappers)
{
$this->hasBeenBootstrapped = true;
foreach ($bootstrappers as $bootstrapper) {
$this['events']->fire('bootstrapping: '.$bootstrapper, [$this]);
$this->make($bootstrapper)->bootstrap($this);
$this['events']->fire('bootstrapped: '.$bootstrapper, [$this]);
}
}
这个方法其实是继承自kernel接口的实现方法,这里的作用就是解析kernel类的bootstraper数组里的那些类,并运行他们的bootstrap方法,这里举个类的例子LoadEnvironmentVariables,从bootstrapWith方法中可以知道,解析出这个实例后运行bootstrap方法,看下
class LoadEnvironmentVariables
{
/**
* Bootstrap the given application.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return void
*/
public function bootstrap(Application $app)
{
if ($app->configurationIsCached()) {
return;
}
$this->checkForSpecificEnvironmentFile($app);
try {
(new Dotenv($app->environmentPath(), $app->environmentFile()))->load();
} catch (InvalidPathException $e) {
//
}
}
protected function checkForSpecificEnvironmentFile($app)
{
//是否console请求
if ($app->runningInConsole() && ($input = new ArgvInput)->hasParameterOption('--env')) {
if ($this->setEnvironmentFilePath(
$app, $app->environmentFile().'.'.$input->getParameterOption('--env')
)) {
return;
}
}
if (! env('APP_ENV')) {
return;
}
//设置app的env文件
$this->setEnvironmentFilePath(
$app, $app->environmentFile().'.'.env('APP_ENV')
);
}
/**
* Load a custom environment file.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @param string $file
* @return bool
*/
protected function setEnvironmentFilePath($app, $file)
{
//设置app的env文件
if (file_exists($app->environmentPath().'/'.$file)) {
$app->loadEnvironmentFrom($file);
return true;
}
return false;
}
可以看出,LoadEnvironmentVariables类的作用,就是设置好容器中environmentFile属性,这就完成了一个启动操作,bootstraper数组里的其他同理。
再来看bootstraper数组里的RegisterProviders引导,进入该类:
class RegisterProviders
{
/**
* Bootstrap the given application.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return void
*/
public function bootstrap(Application $app)
{
$app->registerConfiguredProviders();
}
}
发现调用的是Application.php 的registerConfiguredProviders方法,根据方法名可以猜测这是注册config配置文件中的providers:
public function registerConfiguredProviders()
{
//从配置文件读取一堆的服务提供者
$providers = Collection::make($this->config['app.providers'])
->partition(function ($provider) {
return Str::startsWith($provider, 'Illuminate\\');
});
$providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]);
// dd($providers->collapse()->toArray());
//获取到配置文件的服务提供者后,这里执行load,provider仓储类
(new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
->load($providers->collapse()->toArray());
}
读取完providers后,调用ProviderRepository的load方法
public function load(array $providers)
{
//缓存文件的内容
$manifest = $this->loadManifest();
// First we will load the service manifest, which contains information on all
// service providers registered with the application and which services it
// provides. This is used to know which services are "deferred" loaders.
// 是否重新载入provider缓存文件
if ($this->shouldRecompile($manifest, $providers)) {
//重载后的的里面文件的数组信息
$manifest = $this->compileManifest($providers);
}
// dd($manifest);
// Next, we will register events to load the providers for each of the events
// that it has requested. This allows the service provider to defer itself
// while still getting automatically loaded when a certain event occurs.
// 根据监听触发的延迟加载
foreach ($manifest['when'] as $provider => $events) {
$this->registerLoadEvents($provider, $events);
}
// We will go ahead and register all of the eagerly loaded providers with the
// application so their services can be registered with the application as
// a provided service. Then we will set the deferred service list on it..
// eager立即加载的服务
foreach ($manifest['eager'] as $provider) {
$this->app->register($provider);
}
//把需要延迟加载的服务添加进app容器的deferredServices数组
$this->app->addDeferredServices($manifest['deferred']);
}
这里会对provider进行一个整理,如果有缓存的cache/services.php 文件会先去读,再去和传进来的provider对比,看是否进行更新,然后根据manifest数据的不同键名,对处在不同键名下的provider进行不同操作,比如在eager下会立即去注册该服务,在deferred下面是进行延迟加载,提升性能,而when下面的是监听,根据监听来触发注册deferred的服务(这个在后面会单独有文章来讲)。在这里laravel就会去注册一堆config配置里的provider,也就是为什么以前我们用扩展包的时候,会往config的provider数组加东西了(当然新的都不用了)。
$bootstrappers数组中的BootProviders引导也是一样,会对已经注册的provider执行他们的boot方法。
到这里,kernel的handle里的$this->bootstrap();就执行结束了,容器服务准备了,接下来
//请求通过管道中间件,分发匹配路由
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
通过管道对象,让请求验证一下多个中间件,没问题后,再根据路由匹配,分发到匹配到的闭包或者控制器去执行业务代码,返回结果。
有什么不对或有疑问的地方请大佬们指正:)
本作品采用《CC 协议》,转载必须注明作者和本文链接