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 协议》,转载必须注明作者和本文链接
推荐文章: