Laravel容器初始化
1.缘起
在public目录下的入口文件index.php中,除了加载autoload.php外,接下来就是进行Laravel容器的初始化:
$app = require_once __DIR__.'/../bootstrap/app.php';
此处的$app就是laravel容器。
接下来加载内核Kernel函数,处理请求消息,并返回响应;
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
$response->send();
$kernel->terminate($request, $response);
2. Application初始化
bootstrap目录下的app.php文件中,首先需要创建Application
$app = new Illuminate\Foundation\Application(
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
由此可看到先执行了Application的构造方法
2.1 Application 构造方法
class Application extends Container implements ApplicationContract, HttpKernelInterface
先看Application的定义:继承Container类,并实现ApplicationContract和HttpKernelInterface 接口
Container类中定义了很多重要的属性:$instance $bindings $instances数组
也实现了bing,make,instance等很多重要的方法。
public function __construct($basePath = null)
{
if ($basePath) {
$this->setBasePath($basePath);
}
$this->registerBaseBindings();
$this->registerBaseServiceProviders();
$this->registerCoreContainerAliases();
}
Application的构造方法源码如上,可以看到总共做了 四件事情,每件事情的意义是什么,我们从源码中进行剖析。
2.1.1 为容器设置base path
Application.php中设置basepath的代码如下。可以看到将资源路径,存储路径均进行了instance操作。
public function setBasePath($basePath)
{
$this->basePath = rtrim($basePath, '\/');
$this->bindPathsInContainer();
return $this;
}
/**
* Bind all of the application paths in the container.
*
* @return void
*/
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());
}
那么instance操作具体完成了什么动作呢?我们来看父类Container类中的方法:注意这个instance方法是个公共方法,很多地方在调用;我们先按照路径绑定的方式解读。
/**
* Register an existing instance as shared in the container.
*
* @param string $abstract
* @param mixed $instance
* @return mixed
*/
public function instance($abstract, $instance)
{
$this->removeAbstractAlias($abstract); //判断传入的abstract 是否在Alias中,如果不在则直接返回,如果在,则从AbstractAlias中移除
$isBound = $this->bound($abstract); // 判断传入的abstract 是否在bindings,instances,aliases中,如果在其中一个,则返回true
unset($this->aliases[$abstract]); // 从别名数组中删除
// We'll check to determine if this type has been bound before, and if it has
// we will fire the rebound callbacks registered with the container and it
// can be updated with consuming classes that have gotten resolved here.
$this->instances[$abstract] = $instance; //添加到instances中
if ($isBound) {
$this->rebound($abstract); //如果isBound为true,则重新bound; rebound中调用了make方法,后面我们重点看下
}
return $instance;
}
/**
总结:对路径进行的操作,最终结果实际上是将所有路径,绑定到容器的instances数组中;
样例结果如下:
$this->instances['path'] = '/var/www/laravel/app';
$this->instances['path.base'] = '/var/www/laravel';
$this->instances['path.lang'] = '/var/www/laravel/resources/lang';
$this->instances['path.config'] = '/var/www/laravel/config';
$this->instances['path.public'] = '/var/www/laravel/public';
$this->instances['path.storage'] = '/var/www/laravel/storage';
$this->instances['path.database'] = '/var/www/laravel/database';
$this->instances['path.resources'] = '/var/www/laravel/resources';
$this->instances['path.bootstrap'] = '/var/www/laravel/bootstrap';
2.1.2 完成基础绑定registerBaseBindings();
protected function registerBaseBindings()
{
static::setInstance($this); //将容器赋值给属性instance static::$instance = $container; 可以静态访问容器
$this->instance('app', $this); //
$this->instance(Container::class, $this);
$this->instance(PackageManifest::class, new PackageManifest(
new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
));
}
总结:基础绑定将容器本身设置给自己的静态属性,并将app和container类追加到instances数组,value都指向了容器本身
$this->instances['app'] = object(Illuminate\Foundation\Application);
$this->instances['Illuminate\Container\Container'] = object(Illuminate\Foundation\Application);
2.1.3 注册基础服务registerBaseServiceProviders
Application.php中注册了三个基础服务,分别是 Event,Log 还有路由服务
protected function registerBaseServiceProviders()
{
$this->register(new EventServiceProvider($this));
$this->register(new LogServiceProvider($this));
$this->register(new RoutingServiceProvider($this));
}
是如何注册的呢?来跟踪下register的源码:
public function register($provider, $force = false)
{
//如果已经注册过,则直接从serviceProviders中取出,并返回
if (($registered = $this->getProvider($provider)) && ! $force) {
return $registered;
}
// If the given "provider" is a string, we will resolve it, passing in the
// application instance automatically for the developer. This is simply
// a more convenient way of specifying your service provider classes.
//若传的是字符串,则直接创建对象 new $provider($this);
if (is_string($provider)) {
$provider = $this->resolveProvider($provider);
}
// 如果服务提供者里若存在register方法,则直接自动调用
if (method_exists($provider, 'register')) {
$provider->register();
}
// If there are bindings / singletons set as properties on the provider we
// will spin through them and register them with the application, which
// serves as a convenience layer while registering a lot of bindings.
//如果存在bindings属性,则遍历bindings中的数据,进行bind绑定
if (property_exists($provider, 'bindings')) {
foreach ($provider->bindings as $key => $value) {
$this->bind($key, $value);
}
}
//如果存在singletons属性,则遍历singletons数据,进行singleton
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.
//**如果容器启动,则直接调用服务provider的启动方法,供开发者调用 if (method_exists($provider, 'boot')) { return $this->call([$provider, 'boot']);}**
if ($this->booted) {
$this->bootProvider($provider);
}
return $provider;
}
总结:基础服务注册过程中,默认注册了路由,事件以及日志三种服务。在服务之前未进行过注册的情况下,首先调用服务本身的register方法进行注册,然后对服务提供者的bindings以及singletons属性进行绑定处理,最后在容器启动的前提下,调用服务提供者本身的boot方法进行启动;
实际上,在这个地方,还有两点需要注意的
1)是 bind 方法和 singleton 方法的区别?
2)服务提供者调用注册register方法,具体是完成了什么事情?
2.1.4 注册核心容器别名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],
'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\Hashing\HashManager::class],
'hash.driver' => [\Illuminate\Contracts\Hashing\Hasher::class],
'translator' => [\Illuminate\Translation\Translator::class, \Illuminate\Contracts\Translation\Translator::class],
'log' => [\Illuminate\Log\LogManager::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);
}
}
}
生成的结果参考如下:
$this->aliases['Illuminate\Foundation\Application'] = 'app';
$this->aliases['Illuminate\Contracts\Container\Container'] = 'app';
$this->aliases['Illuminate\Contracts\Foundation\Application'] = 'app';
$this->abstractAliases['app'][] = 'Illuminate\Foundation\Application';
$this->abstractAliases['app'][] = 'Illuminate\Contracts\Container\Container';
$this->abstractAliases['app'][] = 'Illuminate\Contracts\Foundation\Application';
3. 绑定重要的接口
在绑定接口的过程中,我们可以看到均使用的是单例方法。其中Kernel服务的是从Http和Console进入的请求
|--------------------------------------------------------------------------
| Bind Important Interfaces
|--------------------------------------------------------------------------
|
| Next, we need to bind some important interfaces into the container so
| we will be able to resolve them when needed. The kernels serve the
| incoming requests to this application from both the web and CLI.
|
*/
$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
);
4. 服务绑定形式 bind/singleton/instance
服务容器中的服务绑定有三种,分别是bind /singleton/instance
结合服务容器初始化的过程,可以看出
- 完成基础绑定时,用的是instance
- 注册基础服务时,会对服务提供者的bindings属性中的数据进行bind绑定
- 基础服务的注册以及Kernel服务绑定使用的是singleton方法
到底有什么区别呢?
1. singleton源码:其实底层还是调用了bind方法,只不过第三个参数始终为true
public function singleton($abstract, $concrete = null)
{
$this->bind($abstract, $concrete, true);
}
2. bind源码:
public function bind($abstract, $concrete = null, $shared = false)
{
//--------------------1、首先从instances and aliases数组中删除$abstract
$this->dropStaleInstances($abstract);
// 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.
// --------------2、如果第二个参数为null,则将其设置为$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.
// 如果第二个参数不是闭包函数,则应该是class名,会封装成闭包返回。封装的过程中,会判断$abstract和$concrete是否相等,相等就调用容器中的build方法,否则调用容器的make方法解析
if (! $concrete instanceof Closure) {
$concrete = $this->getClosure($abstract, $concrete);
}
// 保存在bindings数组中
$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);
}
}
第三个字段为$shared,true和false有何不同呢?
仅仅体现在bindings数组中的值中
bindings['events']=>[
'concrete'=>$this->app->singleton('events', function ($app) {
return (new Dispatcher($app))->setQueueResolver(function () use ($app) {
return $app->make('Illuminate\Contracts\Queue\Factory');
});
}),
'shared'=>false
]
singleton函数其实就是调用了bind方法,不过$shared参数为true;
也就是说bindings[]中存放内容如下:
bindings['events']=>[
'concrete'=>$this->app->singleton('events', function ($app) {
return (new Dispatcher($app))->setQueueResolver(function () use ($app) {
return $app->make('Illuminate\Contracts\Queue\Factory');
});
}),
'shared'=>true
]
遗留问题:build和make方法有何不同?
3. instance源码:
instance源码在前面已经讨论过;instance是用来向容器中注册已存在的实例
5. build和make方法
在讲bind源码中,获取闭包方法时;如果 if ($abstract == $concrete) 则使用build方法进行构造;否则使用make方法。
/**
* Get the Closure to be used when building a type.
*
* @param string $abstract
* @param string $concrete
* @return \Closure
*/
protected function getClosure($abstract, $concrete)
{
return function ($container, $parameters = []) use ($abstract, $concrete) {
if ($abstract == $concrete) {
return $container->build($concrete);
}
return $container->make($concrete, $parameters);
};
}
5.1 build方法
public function build($concrete)
{
// If the concrete type is actually a Closure, we will just execute it and
// hand back the results of the functions, which allows functions to be
// used as resolvers for more fine-tuned resolution of these objects.
//==如果传入的参数为闭包函数,则直接执行,返回
if ($concrete instanceof Closure) {
return $concrete($this, $this->getLastParameterOverride());
}
//不是闭包的话,说明是一个类名,这里利用反射方法
$reflector = new ReflectionClass($concrete);
// If the type is not instantiable, the developer is attempting to resolve
// an abstract type such as an Interface or Abstract Class and there is
// no binding registered for the abstractions so we need to bail out.
//如果这个类不能被实例化,抛出异常信息
if (! $reflector->isInstantiable()) {
return $this->notInstantiable($concrete);
}
//将类存放到buildStack数组
$this->buildStack[] = $concrete;
//获取构造函数
$constructor = $reflector->getConstructor();
// If there are no constructors, that means there are no dependencies then
// we can just resolve the instances of the objects right away, without
// resolving any other types or dependencies out of these containers.
//如果构造函数为空,说明没有依赖,可以直接实例化返回
if (is_null($constructor)) {
array_pop($this->buildStack);
return new $concrete;
}
//利用php反射获取构造函数的参数
$dependencies = $constructor->getParameters();
// Once we have all the constructor's parameters we can create each of the
// dependency instances and then use the reflection instances to make a
// new instance of this class, injecting the created dependencies in.
$instances = $this->resolveDependencies(
$dependencies
);
//从buildStack中删除这个类
array_pop($this->buildStack);
//返回实例化后的类
return $reflector->newInstanceArgs($instances);
}
可以看出build的主要作用是用来执行闭包函数,或者实例化类名后返回
5.2 make方法
public function make($abstract, array $parameters = [])
{
return $this->resolve($abstract, $parameters);
}
protected function resolve($abstract, $parameters = [])
{
$abstract = $this->getAlias($abstract);
$needsContextualBuild = ! empty($parameters) || ! is_null(
$this->getContextualConcrete($abstract)
);
// If an instance of the type is currently being managed as a singleton we'll
// just return an existing instance instead of instantiating new instances
// so the developer can keep using the same objects instance every time.
//如果该实例已经存在,直接返回,也就是instances注册的实例
if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
return $this->instances[$abstract];
}
$this->with[] = $parameters;
//通过别名获取之前bind方法保存在bindings[]数组中存放的闭包函数
$concrete = $this->getConcrete($abstract);
// We're ready to instantiate an instance of the concrete type registered for
// the binding. This will instantiate the types, as well as resolve any of
// its "nested" dependencies recursively until all have gotten resolved.
if ($this->isBuildable($concrete, $abstract)) {
$object = $this->build($concrete);
} else {
$object = $this->make($concrete);
}
// If we defined any extenders for this type, we'll need to spin through them
// and apply them to the object being built. This allows for the extension
// of services, such as changing configuration or decorating the object.
foreach ($this->getExtenders($abstract) as $extender) {
$object = $extender($object, $this);
}
// If the requested type is registered as a singleton we'll want to cache off
// the instances in "memory" so we can return it later without creating an
// entirely new instance of an object on each subsequent request for it.
//如果是单例注册,放到instances,下次执行make直接返回已经实例化好的对象
if ($this->isShared($abstract) && ! $needsContextualBuild) {
$this->instances[$abstract] = $object;
}
$this->fireResolvingCallbacks($abstract, $object);
// Before returning, we will also set the resolved flag to "true" and pop off
// the parameter overrides for this build. After those two things are done
// we will be ready to return back the fully constructed class instance.
$this->resolved[$abstract] = true;
array_pop($this->with);
return $object;
}