Laravel 源码阅读之二:Illuminate\Foundation\Application 类初始化分析
从入口文件
public/index.php
看,Composer自动加载之后,程序接着执行:$app = require_once __DIR__.'/../bootstrap/app.php';
这里获得一个
Illuminate\Foundation\Application
类的实例$app
,而Application
继承自Container
容器类,所以$app
实例可以调用Container
类的所有非私有方法,也就是说它具备了“容器”的能力。
bootstrap/app.php文件分析
.
.
.
//实例化Application类,传入参数为项目目录路径,如:/home/vagrant/code/Laravel
$app = new Illuminate\Foundation\Application(
realpath(__DIR__.'/../')
);
//绑定类的实现到接口,使得依赖注入是`Illuminate\Contracts\Http\Kerne`接口类
//可以得到`App\Http\Kernel::class`类,另外两个绑定同理。
$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类的初始化
new一个Application对象的时候,将触发Application类的构造函数。
构造函数分析
public function __construct($basePath = null)
{
if ($basePath) {
//(A)路径绑定
//设置basePath,即项目目录(非站点根目录),
//其路径形如:/home/vagrant/code/Laravel
//setBasePath方法内部还调用了bindPathsInContainer方法,其方法分析见(A)部分
$this->setBasePath($basePath);
}
//(B)基础绑定
$this->registerBaseBindings();
//(C)基础服务提供者绑定,结果绑定在$this->serverProvider中
$this->registerBaseServiceProviders();
//(D)核心别名绑定,结果绑定在$this->aliases和$this->abstractAliases中
$this->registerCoreContainerAliases();
}
(A)bindPathsInContainer方法分析
protected function bindPathsInContainer()
{
//path()方法用于给项目目录路径后缀'/app'+传入的$path,这里$path='',
//最后得到项目的app路径
$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());
}
理解该方法的关键是理解Container
类的instance
方法。instance
代码如下:
public function instance($abstract, $instance)
{
//移除类的标识的别名(如果有的话)
$this->removeAbstractAlias($abstract);
//bound()方法的实现只有这句:
//return isset($this->deferredServices[$abstract]) || parent::bound($abstract);
//判断是否有绑定
$isBound = $this->bound($abstract);
//去掉对应别名
unset($this->aliases[$abstract]);
//保存传入的值到instances变量
$this->instances[$abstract] = $instance;
if ($isBound) {
$this->rebound($abstract);
}
return $instance;
}
instance
方法的执行过程还是比较不好理解,绕来绕去的,看不明白为什么要bound、rebound、unset等,但方法最终要实现的目标比较简单,我们直接来看看bindPathsInContainer
方法的执行结果:
就是将应用中一些基本的路径保存到instances
。到此,构造函数中$this->setBasePath($basePath);
的工作才算完成。
(B)registerBaseBindings方法分析
protected function registerBaseBindings()
{
//B-1
static::setInstance($this);
//B-2
$this->instance('app', $this);
//与B-2类似
$this->instance(Container::class, $this);
//B-3
$this->instance(PackageManifest::class, new PackageManifest(
new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
));
}
B-1 setInstance
方法
public static function setInstance(ContainerContract $container = null)
{
return static::$instance = $container;
}
先来看传入的$this
是否符合类型约束。static::setInstance($this);
中,传入的$this
即$app
,属于Application
类的实例,而该类继承了Container
类,Container
类实现了ContainerContact
接口,所以$this instanceof ContainerContact
的值为true
,即$this
作为参数传入setInstance
符合类型约束。
该方法将传入的$this
绑定到父类Container
的$instance
成员变量,看起来非常奇怪,结果也是挺奇怪的——得到一个可以无穷展开的instance
变量:
B-2
这一步也是得到奇怪的结果:$instances
中有$app
,$app
中又有$instances
,如此无限循环嵌套。
B-3
运行结果如下:
这一步将一个PackageManifest
实例添加到$instances
中。至此,registerBaseBindings
的工作完成。
(C) registerBaseServiceProviders方法分析
protected function registerBaseServiceProviders()
{
//C-1 注册事件服务
$this->register(new EventServiceProvider($this));
//C-2 注册日志服务
$this->register(new LogServiceProvider($this));
//C-3 注册路由服务
$this->register(new RoutingServiceProvider($this));
}
C-1
EventServiceProvider
类继承自ServiceProvider
,但自身没有构造函数,所以new一个对象的时候,使用ServiceProvider
的构造函数,其构造函数如下:
public function __construct($app)
{
$this->app = $app;
}
该构造函数将传入的实例绑定到自身的成员变量中。C-2和C-3同理。
register
方法分析
public function register($provider, $force = false)
{
//C-4
//假设$provider='Illuminate\Events\EventServiceProvider'类的实例
//这里$registered=null
if (($registered = $this->getProvider($provider)) && ! $force) {
return $registered;
}
//C-5
//$provider不是字符串,是一个类的实例
if (is_string($provider)) {
$provider = $this->resolveProvider($provider);
}
//判断该类是否存在‘register’方法,有则的调用它
if (method_exists($provider, 'register')) {
$provider->register();
}
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);
}
}
$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;
}
C-4
getProvider
方法:
public function getProvider($provider)
{
//判断是否有服务提供者,有则返回该类的标识,没有则返回null
return array_values($this->getProviders($provider))[0] ?? null;
}
getProviders
方法:
public function getProviders($provider)
{
//$name='Illuminate\Events\EventServiceProvider'
$name = is_string($provider) ? $provider : get_class($provider);
//判断$this->serviceProviders的每一个元素是否是$name的实例,是则保留
return Arr::where($this->serviceProviders, function ($value) use ($name) {
return $value instanceof $name;
});
}
同样的,过程很绕,封装很深,有种进得去出不来的感觉,让人喘不过气。不过,最终达到的目标也很简单:
将三个服务提供者保存到$serviceProviders
和$loadedProviders
中。
(D)registerCoreContainerAliases方法分析
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],
.
.
.
] as $key => $aliases) {
foreach ($aliases as $alias) {
$this->alias($key, $alias);
}
}
其中的alias
方法:
public function alias($abstract, $alias)
{
$this->aliases[$alias] = $abstract;
$this->abstractAliases[$abstract][] = $alias;
}
这一步比较简单,目的是给核心类的的命名空间设置别名,这样以后使用的时候就不用敲一长串的命名空间了。执行结果如下:
以上,一个是命名空间到别名的映射,一个是别名到命名空间的映射。注意到一个别名可能对应多个命名空间,如app
别名对应四个命名空间——所以,这里有个疑问是,以后当我要使用app
代表一个命名空间的时候,它怎么指向我想要的哪一个呢。
小结
初始化的成果如下:
本作品采用《CC 协议》,转载必须注明作者和本文链接
高认可度评论:
回答几个问题:
instance方法的执行过程还是比较不好理解,绕来绕去的,看不明白为什么要bound、rebound、unset等......
一眼看过去是觉得没必要,因为请求进来才初始化,那些东西当然就没有初始化啦。但是,我们这样的认识是基于fpm的运行模型,每一个请求都是独立,请求完毕之后php程序自动销毁。然而,要是你的PHP程序是常驻内存呢,比如swoole应用,那么就不是一回事了。
关于
无限嵌套
其实就是下面这两句代码的效果,把自身对象绑定到自身的一个变量中,相当于自身变量保存自身的一个引用,所以看起来像无限嵌套,其实在内存中只有一个对象
这其实是一种软件设计的方法,你要是问我为什么我在GoF中没有找到这种模式啊,我只能说设计模型不是一种成文的知识体系,都是经验之谈而已,是前辈们在软件设计开发中的血泪教训总结出来的,并不是“银弹”。
其实你在第一次写单例的时候不也写过类似的代码吗?
对于
app
这个别名,当你使用app('app')
获取容器实例时,已经不会走别名解析,还是上面说到的已经绑定到instances数组的实例,就会直接取,所以app('app')获取到的就是
Illuminate\Foundation\Application
的一个实例。我们可以关注其它,比如app('queue'),我们知道queue
绑定三个命名空间,当我们 app('queue') 时,由于'queue'就是别名
getAlias()
会直接返回,我们看getConcrete()
,里面调用getContextualConcrete($abstract)
,继续看可以看到,所以清楚了吧?其中比较绕的是关于容器如何去实例化一个对象,中间有很多寻找对象类的步骤。
写的很不错,期待后续好文,共同加油。
@lx1036 谢谢!
多个命名空间,composer似乎生成了映射文件。。
回答几个问题:
instance方法的执行过程还是比较不好理解,绕来绕去的,看不明白为什么要bound、rebound、unset等......
一眼看过去是觉得没必要,因为请求进来才初始化,那些东西当然就没有初始化啦。但是,我们这样的认识是基于fpm的运行模型,每一个请求都是独立,请求完毕之后php程序自动销毁。然而,要是你的PHP程序是常驻内存呢,比如swoole应用,那么就不是一回事了。
关于
无限嵌套
其实就是下面这两句代码的效果,把自身对象绑定到自身的一个变量中,相当于自身变量保存自身的一个引用,所以看起来像无限嵌套,其实在内存中只有一个对象
这其实是一种软件设计的方法,你要是问我为什么我在GoF中没有找到这种模式啊,我只能说设计模型不是一种成文的知识体系,都是经验之谈而已,是前辈们在软件设计开发中的血泪教训总结出来的,并不是“银弹”。
其实你在第一次写单例的时候不也写过类似的代码吗?
对于
app
这个别名,当你使用app('app')
获取容器实例时,已经不会走别名解析,还是上面说到的已经绑定到instances数组的实例,就会直接取,所以app('app')获取到的就是
Illuminate\Foundation\Application
的一个实例。我们可以关注其它,比如app('queue'),我们知道queue
绑定三个命名空间,当我们 app('queue') 时,由于'queue'就是别名
getAlias()
会直接返回,我们看getConcrete()
,里面调用getContextualConcrete($abstract)
,继续看可以看到,所以清楚了吧?其中比较绕的是关于容器如何去实例化一个对象,中间有很多寻找对象类的步骤。
@JimChen 感谢详细的解答,说的很清楚了:+1:
不错哦,小lala :kissing_heart: :kissing_heart: :heart_eyes: :heart_eyes: