Laravel生命周期

最近以Laravel5.5版本为例仔细研究了下Laravel的生命周期,其实还蛮有意思的,以前都是看别人的,看完就忘…


Laravel的整个生命周期总结如下图:

laravel生命周期

开始

首先从public/index.php 入口开始,可以看到里面就做了4件事。

一、定义框架的启动时间,单位毫秒 对应代码

1
define('LARAVEL_START', microtime(true));

二、注册并加载composer自动加载器,对应代码

1
require __DIR__.'/../vendor/autoload.php';

三、获取Application应用实例,对应代码

1
$app = require_once __DIR__.'/../bootstrap/app.php';

四、运行容器,对应代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 从容器中解析出http内核实例
// make方法根据绑定的http内核接口,返回的是实现接口的 App\Http\Kernel 实例
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
// 因为继承 调用 Illuminate\Foundation\Http\Kernel 的handle处理请求
$response = $kernel->handle(
// 从服务器变量中创建的新的 Illuminate Http请求
$request = Illuminate\Http\Request::capture()
);
// 返回的response是 Illuminate\Http\Response 实例
// 然后调用send方法 设置http头,输出content也就输出了内容
$response->send();
//App\Http\Kernel 继承 Illuminate\Foundation\Http\Kernel
// 在任何可终止的中间件上调用terminate方法
$kernel->terminate($request, $response);

如何获取应用实例

做的最多事情的其实就是上面的第三和第四部分。先来看下如何获取应用实例的。根据代码首先是加载的 bootstrap/app.php 文件。代码也很简单主要做了下面几个事情。

实例化

可以看到进来就是创建应用也就是Application实例对象也是容器对象。根据官方的注释可以看到:

我们要做的第一件事就是创建一个新的laravel应用实例,它连接着laravel的各个部分,同时它还是系统绑定各个部分的一个IOC容器。

这里做了最多的事情,对应代码如下,他传递进去的参数是一个基础路径

1
2
3
$app = new Illuminate\Foundation\Application(
realpath(__DIR__.'/../')
);

然后来看下实例化做了什么,对应文件 vendor/laravel/framework/src/Illuminate/Foundation/Application.php 的构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* Create a new Illuminate application instance.
* 创建一个新的 Illuminate 应用实例
*
* @param string|null $basePath
* @return void
*/
public function __construct($basePath = null)
{
// 如果传递了基础路径
if ($basePath) {
// 给实例设置基础路径
$this->setBasePath($basePath);
}
// 给容器注册基础绑定
$this->registerBaseBindings();
// 注册所有的基础服务提供者
$this->registerBaseServiceProviders();
// 给容器(也是application实例)注册核心类别名
$this->registerCoreContainerAliases();
}

这个构造函数主要是做了4个工作。

设置框架运行的基础路径

具体函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* Set the base path for the application.
* 给应用设置基础路径
*
* @param string $basePath
* @return $this
*/
public function setBasePath($basePath)
{
// 设置laravel安装的基础路径 去掉路径末尾的 '/'
$this->basePath = rtrim($basePath, '\/');
// 绑定laravel的各种主要部分的路径
// 具体有laravel根目录下的 app,lang,config,public,storage,database,resources,bootstraps等
$this->bindPathsInContainer();
// 当前类的引用
return $this;
}
给容器注册基础绑定

具体代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* Register the basic bindings into the container.
*
* @return void
*/
protected function registerBaseBindings()
{
// 把当前的application引用设置为当前全局可用容器实例
static::setInstance($this);
// 将现有实例注册为容器中的共享实例。
$this->instance('app', $this);
// 把container也注册为共享实例
$this->instance(Container::class, $this);
// 创建一个新的包清单实例 同时注册为共享实例,也就是vendor目录下加载了多少的包
$this->instance(PackageManifest::class, new PackageManifest(
new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
));
}
注册所有的基础服务提供者
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* Register all of the base service providers.
*
* @return void
*/
protected function registerBaseServiceProviders()
{
// 给当前实例注册事件服务提供者 - 里面是一个事件调度实例
$this->register(new EventServiceProvider($this));
// 给当前application实例注册日志服务提供者 - 里面是一个Monolog实例
$this->register(new LogServiceProvider($this));
// 给当前实例注册路由服务 - 里面是一个vendor/laravel/framework/src/Illuminate/Routing/Router.php实例
$this->register(new RoutingServiceProvider($this));
}
给容器(也是application实例)注册核心类别名
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
48
/**
* Register the core class aliases in the container.
*
* @return void
*/
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],
'encrypter' => [\Illuminate\Encryption\Encrypter::class, \Illuminate\Contracts\Encryption\Encrypter::class],
'db' => [\Illuminate\Database\DatabaseManager::class],
'db.connection' => [\Illuminate\Database\Connection::class, \Illuminate\Database\ConnectionInterface::class],
'events' => [\Illuminate\Events\Dispatcher::class, \Illuminate\Contracts\Events\Dispatcher::class],
'files' => [\Illuminate\Filesystem\Filesystem::class],
'filesystem' => [\Illuminate\Filesystem\FilesystemManager::class, \Illuminate\Contracts\Filesystem\Factory::class],
'filesystem.disk' => [\Illuminate\Contracts\Filesystem\Filesystem::class],
'filesystem.cloud' => [\Illuminate\Contracts\Filesystem\Cloud::class],
'hash' => [\Illuminate\Contracts\Hashing\Hasher::class],
'translator' => [\Illuminate\Translation\Translator::class, \Illuminate\Contracts\Translation\Translator::class],
'log' => [\Illuminate\Log\Writer::class, \Illuminate\Contracts\Logging\Log::class, \Psr\Log\LoggerInterface::class],
'mailer' => [\Illuminate\Mail\Mailer::class, \Illuminate\Contracts\Mail\Mailer::class, \Illuminate\Contracts\Mail\MailQueue::class],
'auth.password' => [\Illuminate\Auth\Passwords\PasswordBrokerManager::class, \Illuminate\Contracts\Auth\PasswordBrokerFactory::class],
'auth.password.broker' => [\Illuminate\Auth\Passwords\PasswordBroker::class, \Illuminate\Contracts\Auth\PasswordBroker::class],
'queue' => [\Illuminate\Queue\QueueManager::class, \Illuminate\Contracts\Queue\Factory::class, \Illuminate\Contracts\Queue\Monitor::class],
'queue.connection' => [\Illuminate\Contracts\Queue\Queue::class],
'queue.failer' => [\Illuminate\Queue\Failed\FailedJobProviderInterface::class],
'redirect' => [\Illuminate\Routing\Redirector::class],
'redis' => [\Illuminate\Redis\RedisManager::class, \Illuminate\Contracts\Redis\Factory::class],
'request' => [\Illuminate\Http\Request::class, \Symfony\Component\HttpFoundation\Request::class],
'router' => [\Illuminate\Routing\Router::class, \Illuminate\Contracts\Routing\Registrar::class, \Illuminate\Contracts\Routing\BindingRegistrar::class],
'session' => [\Illuminate\Session\SessionManager::class],
'session.store' => [\Illuminate\Session\Store::class, \Illuminate\Contracts\Session\Session::class],
'url' => [\Illuminate\Routing\UrlGenerator::class, \Illuminate\Contracts\Routing\UrlGenerator::class],
'validator' => [\Illuminate\Validation\Factory::class, \Illuminate\Contracts\Validation\Factory::class],
'view' => [\Illuminate\View\Factory::class, \Illuminate\Contracts\View\Factory::class],
] as $key => $aliases) {
foreach ($aliases as $alias) {
$this->alias($key, $alias);
}
}
}

至此完成了实例的初始化,添加了一些基础属性等。

绑定核心接口

根据官方注释第二步需要进行核心接口的绑定。 需要注意 app是 application的实例,然后继承了Illuminate\Container\Container; 所以这里的singleton方法其实是Container里的给容器注册绑定。对应代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 给容器注册共享绑定-http内核
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);
// 给容器注册共享绑定-控制台 (artisan 命令?)
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);
// 给容器注册共享绑定-异常类
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);

这个绑定核心接口部分做了3件事,主要是通过 singleton 方法,参数是一个抽象类,一个具体实现类。完成了HTTP内核注册,控制台注册和异常处理注册。

主要看下 vendor/laravel/framework/src/Illuminate/Container/Container.php 文件中的 bind 方法,因为在 singleton 方法中调用了 bind.

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
/**
* Register a binding with the container.
*
* @param string $abstract
* @param \Closure|string|null $concrete
* @param bool $shared
* @return void
*/
public function bind($abstract, $concrete = null, $shared = false)
{
// If no concrete type was given, we will simply set the concrete type to the
// abstract type. After that, the concrete type to be registered as shared
// without being forced to state their classes in both of the parameters.
// 第一步删除所有陈旧的实例和别名
$this->dropStaleInstances($abstract);
if (is_null($concrete)) {
$concrete = $abstract;
}
// If the factory is not a Closure, it means it is just a class name which is
// bound into this container to the abstract type and we will just wrap it
// up inside its own Closure to give us more convenience when extending.
// 如果工厂不是Closure,这意味着它只是一个类名,它被绑定到这个容器中的抽象类型,
// 我们将它包装在它自己的Closure中,以便在扩展时给我们更多的便利。
if (! $concrete instanceof Closure) {
$concrete = $this->getClosure($abstract, $concrete);
}
$this->bindings[$abstract] = compact('concrete', 'shared');
// If the abstract type was already resolved in this container we'll fire the
// rebound listener so that any objects which have already gotten resolved
// can have their copy of the object updated via the listener callbacks.
if ($this->resolved($abstract)) {
$this->rebound($abstract);
}
}

可以看到最终这些绑定关系都放在了 Container 实例的 bindings 属性中,key是抽象类名,值是具体实现类名。

然后返回实例,对应代码: return $app; 至此完成了应用的实例化。

如何运行容器

在完成了容器的初始化后需要运行容器,在这里又需要做4件事。

从应用实例中解析出HTTP内核实例对象

主要代码是 publuc/index.php 文件中的 $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); 这一行。主要看下 make 方法。在文件 vendor/laravel/framework/src/Illuminate/Foundation/Application.php 中,不过最后它又调用了父类的 make 方法,在 vendor/laravel/framework/src/Illuminate/Container/Container.php 中。最终可以看到调用的是父类的 resolve 方法。

简单说明下吧,该方法主要是从容器的实例对象中根据传入的参数抽象类名,获取到了当初绑定的对应实现类,不过是一个闭包,然后根据闭包实例化了实现类并返回。在上面的分析中 Container 实例的 bindings 属性中绑定过HTTP内核了,对应的实现类是 app/Http/Kernel.php 所以返回的 $kernel 就是这个类的实例。

调用http内核的 handle 方法处理请求

上一步已经获取了HTTP内核的实例,然后开始处理请求,对应代码

1
2
3
4
$response = $kernel->handle(
// 从服务器变量中创建的新的 Illuminate Http请求
$request = Illuminate\Http\Request::capture()
);

主要这里调用的 handle 方法,因为返回的kernel是 App\Http\Kernel.php 的实例,而它又继承自 vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php 所以 handle 方法其实是在父类中的。

不过处理这个前需要看下那个参数,他需要的参数是一个 request,具体应该是 Illuminate Http请求

构建Request请求

在代码 $request = Illuminate\Http\Request::capture()

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Create a new Illuminate HTTP request from server variables.
*
* @return static
*/
public static function capture()
{
// 开启http请求参数重写
static::enableHttpMethodParameterOverride();
// 使用PHP的全局变量创建一个新的请求
return static::createFromBase(SymfonyRequest::createFromGlobals());
}

然后来看下 SymfonyRequest::createFromGlobals() 方法干了什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static function createFromGlobals()
{
.........
// 初始化了http请求的各种参数和信息
$request = self::createRequestFromFactory($_GET, $_POST, [], $_COOKIE, $_FILES, $server);
if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded')
&& \in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), ['PUT', 'DELETE', 'PATCH'])
) {
parse_str($request->getContent(), $data);
$request->request = new ParameterBag($data);
}
return $request;
}

他主要就是用PHP里的超全局变量设置到了一个 vendor/symfony/http-foundation/Request.php 的实例对象中,然后返回了实例。

从Symfony实例创建Illuminate请求

回到 vendor/laravel/framework/src/Illuminate/Http/Request.phpcreateFromBase 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static function createFromBase(SymfonyRequest $request)
{
if ($request instanceof static) {
return $request;
}
$content = $request->content;
$request = (new static)->duplicate(
$request->query->all(),
$request->request->all(),
$request->attributes->all(),
$request->cookies->all(),
$request->files->all(),
$request->server->all()
);
$request->content = $content;
$request->request = $request->getInputSource();
return $request;
}

注意看其中的 duplicate 方法,因为 当前类继承了Symfony下的http-foundation\Request 而传递进来的request刚好是http-foundation\Request的实例 所以duplicate方法也就是 http-foundation\Request 中的 duplicate 方法。其中对request实例进行了 clone 操作,并把下面的参数赋值到了一些变量属性上,重新返回了Request实例。只不过当前返回的实例已经变成 vendor/laravel/framework/src/Illuminate/Http/Request.php 的实例了。

回到handle函数

获取到request实例后,回来看下 handle 函数,其中又调用了 sendRequestThroughRouter 方法

1
2
3
4
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());

然后就返回了一个 Illuminate\Http\Responseresponse 实例。

返回请求结果

在获取到Request的请求结果后通过 $response->send(); 来输出结果。send方法在 vendor/symfony/http-foundation/Response.php 中,然后通过调用父类 vendor/symfony/http-foundation/Response.phpsendContent 方法,直接 echo 出了内容就算返回了。

终止应用

在返回处理结果后 通过调用 $kernel->terminate($request, $response); 来终止应用,到此结束。


-------------The End-------------