2.1 Laravel 生命周期和网络请求生命周期还真不是一回事儿

人呐,就是很奇怪,我们都知道 Laravel 源代码蕴含的巨大价值,但是很多使用 Laravel 或者 PHP 作为吃饭工具的码农们以及学习者,很少有人真正的去研究一下 Laravel 源码,说到原因呢,无非就是 “懒” 和 “恐惧”,看似“懒”是主要原因,仔细想想的话,似乎不是那么回事儿, “恐惧” 才是,人如果无法克服自己的心里障碍,就很难行动起来,而 “懒” 通常只会导致拖延而已,一件事情如果你拖了太久,就不再只是简单的 “懒” 的问题了。“舒适区” 很舒服,但是舒适区面积会随着时间加速缩小,我们又能舒服多久。好了,不废话了,咱们开始看看代码吧,真的没多少,也没必要了解得太具体,太八股了也不好。

laravel request and response

上回书咱们跟着网络请求已经进入到了 public\index.php 入口文件,入口文件中虽然只有 10 来行代码,但是把流程却交代得很清楚,作者作为一个强迫症,代码写的一丝不苟,注释的缩进都一模一样,光是这一点,没有多少人能做到,作者如此用心良苦,减轻了学习者很多痛苦。用户的网络请求就是传递给 Laravel 应用创建的 HTTP 内核 进行网络请求处理,并把处理后的网络响应结果返回给用户的。网络请求就是在第三步被 HTTP $Kernal 对象的 handle 函数接收并完成处理的,handle函数接收的参数就是用户传递过来的网络请求 。由于上述 $kernel->handle() 是如此的重要,我们真的需要先跟它打打招呼交个朋友才行,那咱们就先走个回头路,看一看 网络请求 传递给 Laravel HTTP Kernel 之前,Laravel 都做了什么,虽然这部分内容不是网络请求生命周期走的流程,但是却是 Laravel 生命诞生的地方,咱们这次的专题是 Laravel 的生命周期,所以这部分内容还是要提一提的,到了这里很多朋友应该可以开始解开疑惑了,是的,我们这个专题其实涉及到了两个生命周期,一个是 网络请求的生命周期,一个就是 Laravel 生命周期。由于这两口子水乳交融,夫唱妇随,大部分时间他们都抱在一块儿羞羞,导致很多人把它当作一个东西了,而这也是为什么很多人会觉得网上的教程有些地方总觉得不对劲的原因。从上图中,咱们已经可以看出来,网络响应(Response)已经在第三步就已经返回给用户了。网络响应 返回后,Laravel 的生命周期并未结束,它还有一些善后工作需要处理,也就是第四步 “请求结束,进行回调” ,执行完了这一步之后,Laravel 才算是到了一个终结点。再贴一次代码吧,不少技术人员看不见代码就难受: 

 3. 最关键的步骤,接受请求,对请求进行处理,返回请求处理的结果
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
$response->send();

4. 请求结束,进行回调
$kernel->terminate($request, $response);

本来写这篇文章是想着要不就不区分它们了吧,像网上其他教程那样当成一个东西写就行了,但是按照这种逻辑会让人产生混乱,不利于大家真正理解 Laravel 或者说其他网络开发框架,所以还是做了一个区分,先把网络请求的生命周期交代清楚了,咱们再去详细分析 Laravel 的生命周期,思路上就会更清晰明了些,有些东西八股一下还是有必要的。接下来,咱们就可以暂时把网络请求放一边,看一看 Laravel 的创建过程:

 

1.  加载 Composer 生成的 autoload.php 自动加载文件

这一步没啥好解释的,只要是使用 Composer 构建的框架和项目,都会有这一步,不解释了,如果你对 Composer 不熟悉的话,有时间的时候可以补一补这部分的缺口《 Composer PHP 依赖管理工具 》。

 

 

2.  创建 Laravel 应用容器对象 $app ( Service Container ) 

  $app = new Illuminate\Foundation\Application( dirname(__DIR__) );
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);
return $app;

在这里基本也没啥难以理解的东西,创建了 $app 容器对象,绑定了 HTTP Kernal 和 Console Kernel,分别用来处理 HTTP 网络请求和 CLI 请求( 执行 php artisan 相关命令请求),还绑定了用来处理应用运行异常的调试处理器。

  # Laravel $app 创建时的构建函数
public function __construct($basePath = null)
{
if ($basePath) {
$this->setBasePath($basePath);
}
$this->registerBaseBindings();
$this->registerBaseServiceProviders();
$this->registerCoreContainerAliases();
}

上面是 $app 实例创建的具体逻辑,我们看到也没啥难以理解的内容,无非就是设定路径,注册基础绑定信息,注册基础 Service Provider 和 Aliases,而做这些工作的意义就是可以帮助我们更方便的访问和获取项目目录中的文件信息,加载配置文件,加载自定义的类,加载Service,aliases 定义了如何把开发框架中最基础的服务注册到 Service Container 中,看了这部分代码,咱们就知道为啥 app('files')app ('Illuminate\Filesystem\Filesystem') 这两种使用方法都可以拿到 FileSystem 服务的原因了。

  public function registerCoreContainerAliases()
{
foreach ([
'app' => [\Illuminate\Foundation\Application::class, \Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class, \Psr\Container\ContainerInterface::class],
'auth' => [\Illuminate\Auth\AuthManager::class, \Illuminate\Contracts\Auth\Factory::class],
'auth.driver' => [\Illuminate\Contracts\Auth\Guard::class],
'blade.compiler' => [\Illuminate\View\Compilers\BladeCompiler::class],
'cache' => [\Illuminate\Cache\CacheManager::class, \Illuminate\Contracts\Cache\Factory::class],
'files' => [\Illuminate\Filesystem\Filesystem::class],
'filesystem' => [\Illuminate\Filesystem\FilesystemManager::class, \Illuminate\Contracts\Filesystem\Factory::class],
.

'view' => [\Illuminate\View\Factory::class, \Illuminate\Contracts\View\Factory::class],
] as $key => $aliases) {
foreach ($aliases as $alias) {
$this->alias($key, $alias);
}
}
}

其实很多人之所以不愿意去看代码,有一个主要的原因就是没有理解 Service,Service Container,Service Provider,Facades,Contracts 这些核心概念以及他们之间的关联,如果你对 Laravel 核心概念不清楚,我也真的不建议你看源码,你会想把电脑砸掉的 ~

3.  创建 Laravel HTTP Kernel 核心,接收用户的网络请求,处理并返回响应结果

咱们终于又回来了,咱们先来看看创建 Kernel 的时候都做了啥,HTTP 内核继承自 Illuminate\Foundation\Http\Kernel 类,这个类中拥有三个核心成员变量

  protected $app;
protected $router;
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, # 注册Service Providers
\Illuminate\Foundation\Bootstrap\BootProviders::class, # 注册启动器
];

这三个核心成员变量就是引导咱们更快理清 $app->kernel->handle(网络请求) 的最好导游:

$app 咱们已经说了足够多,就是应用容器,我们所有的工作都在这个容器中进行。

$router 路由对象,它提供路由相关的服务,帮助我们把网络请求分配给对应的路由进行逻辑处理,然后把处理的结果(网络响应)返回给我们,我们在web.php中定义的路由就是由它来管理的。

$bootstrappers 数组,这个数组中的任务项在网络请求被处理前运行,我们可以看到 环境检查,配置加载,异常处理,Facedes 门面注册,ServiceProvider 注册 等等任务都需要在网络请求被处理前被首先执行,而且这些任务是有前后顺序的,排在前面的会首先执行,这也很容易理解,因为不管是 Facades 还是 Service Providers 都是定义在 config 目录中的 app.php 文件中的,只有加载来配置之后才能注册门面和Service Providers。

然后咱们再来看看 $app->kernel 的构造函数,在这里咱们看到了一个脸熟的不能再熟的老朋友 — Middleware ,在 Kernel 以及它的基类 Illuminate\Foundation\Http\Kernel 中定义了一系列的 middlewares ,借助这些中间件,就可以完成对用户请求的过滤和安全检查等等功能。

  public function __construct(Application $app, Router $router)
{
$this->app = $app;
$this->router = $router;

$router->middlewarePriority = $this->middlewarePriority;
foreach ($this->middlewareGroups as $key => $middleware) {
$router->middlewareGroup($key, $middleware);
}
foreach ($this->routeMiddleware as $key => $middleware) {
$router->aliasMiddleware($key, $middleware);
}
}

至此咱们还是终于可以绕回来进入  $app->kernel->handle($request) 看一看它是如何处理网络请求的吧,下面这张图结合第一张图会更清楚一些,接下来的内容实在不想贴代码了:

laravel kernel handle 

4.  结束请求,进行回调,终止 Laravel 应用,Laravel 到此也完成了它的历史使命。中间件中有一类的中间件,terminable middleware 的处理逻辑就是在这个阶段执行的。

文章写到这里基本上就结束了,太多细节我们并没有涉及,要靠诸君亲力亲为了,我呢,还是那些话,不要在一开始的阶段就去学习 laravel 的生命周期,等你用laravel 工作了一段时间,对它的使用方法和核心概念都清楚了之后再来学习,基本上就是一件很容易的事了。最后附上一张大图,梳理一下整个流程,也作为文章的结束吧。

Laravel 生命周期图解

作图真累,图看不清的话,就单开一个网页看它吧。