第三选择

刻意练习,日渐精进

0%

从零学习Laravel框架-Laravel的生命周期

各位童鞋,今天我们来研究一下 Laravel 的生命周期,看看 Laravel 都在做些什么,话不多说,先贴代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 定义一个常量,记录框架启动的时间
define('LARAVEL_START', microtime(true));

// 注册 composer 的自动加载机制
require __DIR__.'/../vendor/autoload.php';

// 初始化容器
$app = require_once __DIR__.'/../bootstrap/app.php';

// app.php 中已经绑定了 Illuminate\Contracts\Http\Kernel::class
// 所以这里得到的是 App\Http\Kernel::class 的实例
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

// 获取请求,处理完成后得到响应
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);

// 输出响应到客户端
$response->send();

// 调用中间件的 terminate 方法,以及容器的 terminate 方法,结束
$kernel->terminate($request, $response);

关于自动加载机制,如果有不了解的同学,可以先阅读一下这篇文章: PHP 的自动加载机制,至于 Composer 是怎么实现自动加载的,我们这篇文章里也给大家唠唠:Composer的自动加载机制

容器初始化

首先我们看看容器初始化都做了什么:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

// 实例化容器
$app = new Illuminate\Foundation\Application(
$_ENV['APP_BASE_PATH'] ?? 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;

Application 就是我们项目里承上启下的服务容器,那实例化的时候都做了些什么呢,依然是贴代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
public function __construct($basePath = null)
{
// 设定项目的基础路径
if ($basePath) {
$this->setBasePath($basePath);
}

$this->registerBaseBindings();

$this->registerBaseServiceProviders();

$this->registerCoreContainerAliases();
}

第一步,设定路径, setBasePath() 可不单单设定一个 base_path 这么简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public function setBasePath($basePath)
{
$this->basePath = rtrim($basePath, '\/');

$this->bindPathsInContainer();

return $this;
}

protected function bindPathsInContainer()
{
$this->instance('path', $this->path());
$this->instance('path.base', $this->basePath());
$this->instance('path.lang', $this->langPath());
$this->instance('path.config', $this->configPath());
$this->instance('path.public', $this->publicPath());
$this->instance('path.storage', $this->storagePath());
$this->instance('path.database', $this->databasePath());
$this->instance('path.resources', $this->resourcePath());
$this->instance('path.bootstrap', $this->bootstrapPath());
}

/**
* 将现有实例注册为容器中的共享实例
* @package Illuminate\Container\Container
*/
public function instance($abstract, $instance)
{
// 将抽象类从 $this->$abstractAliases 中移除
$this->removeAbstractAlias($abstract);

// 判断传递的抽象类是否已经被绑定
$isBound = $this->bound($abstract);

// 将抽象类从 $this->aliases 中移除
unset($this->aliases[$abstract]);

// 注册到共享实例中
$this->instances[$abstract] = $instance;

// 已绑定则调用 rebound 的匿名函数
if ($isBound) {
$this->rebound($abstract);
}

return $instance;
}

可以看到,除了定义 bash_path, Application 还将各个模块的路径映射关系保存到 $application->instances 共享实例中。

接着,我们看看 registerBaseBindings(),这个步骤主要是绑定一些基础的对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected function registerBaseBindings()
{
// 设置 static::$instance = $this
static::setInstance($this);

// 绑定 $application 到 共享实例 $this->instances['app'] 中
$this->instance('app', $this);

// 绑定 $application 到 $this->instances['Illuminate\Container\Container'] 中
$this->instance(Container::class, $this);

// 绑定 PackageManifest 实例到 $this->instances['Illuminate\Foundation\PackageManifest']
$this->instance(PackageManifest::class, new PackageManifest(
new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
));
}

再往下看,registerBaseServiceProviders() 就是注册基础的服务提供者了:

1
2
3
4
5
6
protected function registerBaseServiceProviders()
{
$this->register(new EventServiceProvider($this));
$this->register(new LogServiceProvider($this));
$this->register(new RoutingServiceProvider($this));
}

我们主要看看这个 register() 方法,这是 Laravel 框架精华的部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public function register($provider, $force = false)
{
// 如果已注册,且不覆盖则直接返回
if (($registered = $this->getProvider($provider)) && ! $force) {
return $registered;
}

// 如果传入的是字符串,则进行实例化的操作,所以我们可以以 Provider::class 的形式传参
if (is_string($provider)) {
$provider = $this->resolveProvider($provider);
}

// 如果服务提供者有 register 的方法,则调用
if (method_exists($provider, 'register')) {
$provider->register();
}

// 如果服务提供者有 bindings / singletons 属性,则调用容器的 bind 方法
// 将类和实现绑定
if (property_exists($provider, 'bindings')) {
foreach ($provider->bindings as $key => $value) {
$this->bind($key, $value);
}
}

if (property_exists($provider, 'singletons')) {
foreach ($provider->singletons as $key => $value) {
$this->singleton($key, $value);
}
}

// 将 $provider 维护进容器的 $serviceProviders 和 $loadedProviders 里
$this->markAsRegistered($provider);

// If the application has already booted, we will call this boot method on
// the provider class so it has an opportunity to do its boot logic and
// will be ready for any usage by this developer's application logic.
if ($this->booted) {
$this->bootProvider($provider);
}

return $provider;
}

大家应该都发现了,registersingleton 最终调用的其实都是 bind() 方法将类和实现绑定在 $bindings 的变量里,这个也是 IoC 容器的核心,通过事先绑定类和实现,在需要用到的时候再通过 make 方法实例化。

对于 IoC 容器不理解的同学,我们可以看看这篇:

基础的服务提供者注册完,还需要做下一步,注册服务别名 registerCoreContainerAliases()。这主要是为了在解析服务时,可以不用输入那么长的类名, 可以精简我们的代码,也更好地去阅读。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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],
'cache.store' => [\Illuminate\Cache\Repository::class, \Illuminate\Contracts\Cache\Repository::class],
'config' => [\Illuminate\Config\Repository::class, \Illuminate\Contracts\Config\Repository::class],
'cookie' => [\Illuminate\Cookie\CookieJar::class, \Illuminate\Contracts\Cookie\Factory::class, \Illuminate\Contracts\Cookie\QueueingFactory::class],
···
] as $key => $aliases) {
foreach ($aliases as $alias) {
$this->alias($key, $alias);
}
}
}

public function alias($abstract, $alias)
{
$this->aliases[$alias] = $abstract;

$this->abstractAliases[$abstract][] = $alias;
}

注册完服务别名,容器初始化就算完成了。接着容器还会再将几个核心实现绑定到 $bindings 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$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
);

至此,引入容器的工作就完成了,接下来就该要运行容器了。

运行容器

上面很大的篇幅描述了初始化容器的工作,毕竟工欲善其事,必先利其器。继续往下走:

1
2
3
4
5
6
7
8
9
10
11
12
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

// 处理请求并得到响应
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);

// 输出响应
$response->send();

// 终止销毁容器
$kernel->teminate($request, $response);

还记得这个 kernel 吗?这就是我们在初始化容器时绑定到容器中的,能叫这个名字,必须很关键了。通过容器 make 得到 $kernel 实例,由 $kernel 创造一个大黑盒,在黑盒内处理请求并得到响应,然后将响应发送给客户端。关于这个大黑盒里发生的事情,我们留到后面的文章里再来讲吧。

最后会先将中间件排序,并调用中间件的 terminate() 方法(如果有的话),然后调用容器中 $terminatingCallbacks 里的方法,完成终止容器的步骤。

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Call the terminate method on any terminable middleware.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Http\Response $response
* @return void
*/
public function terminate($request, $response)
{
$this->terminateMiddleware($request, $response);

$this->app->terminate();
}

最后,框架会调用 Illuminate\Foundation\Bootstrap\HandleException
handleShutdown() 方法,处理出现的致命错误。这个方法在框架启动时,通过 register_shutdown_function() 注册。

1
2
3
4
5
6
7
public function handleShutdown()
{
// 如果最后有致命错误,将异常记录下来
if (! is_null($error = error_get_last()) && $this->isFatal($error['type'])) {
$this->handleException($this->fatalExceptionFromError($error, 0));
}
}

Laravel 的生命周期我们就讲到这里,下期我们再接着讲讲 Laravel 路由和请求。