关于服务容器的基本概念,你可以看这几篇文章:
《简单理解Laravel的核心概念----服务容器、服务提供者、契约》
在这篇文章中,你会了解到:
- 服务是如何被注册到服务容器的
- 服务是究竟怎样从容器中解析的
我们知道,laravel的服务容器,会有两方面的工作:
- 注册基础服务
- 管理所需要的创建的类和依赖
注册基础服务
想要了解服务容器,我们需要去看看它的相关源码。在laravel中,服务容器类是Illuminate\Foundation\Application类。我们打开这个类:
namespace Illuminate\Foundation;
...
class Application extends Container implements ApplicationContract, HttpKernelInterface
{
...
/**
* Create a new Illuminate application instance.
*
* @param string|null $basePath
* @return void
*/
public function __construct($basePath = null)
{
if ($basePath) {
$this->setBasePath($basePath);
}
$this->registerBaseBindings();
$this->registerBaseServiceProviders();
$this->registerCoreContainerAliases();
}
...
...
/**
* Register the basic bindings into the container.
*
* @return void
*/
protected function registerBaseBindings()
{
static::setInstance($this);
$this->instance('app', $this);
$this->instance(Container::class, $this);
$this->singleton(Mix::class);
$this->instance(PackageManifest::class, new PackageManifest(
new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
));
}
...
...
}
在App容器创建之初,我们看到构造方法中完成了三个处理功能:
- registerBaseBindings():将App实例「服务容器」自身注册到「服务容器」
- registerBaseServiceProviders():注册应用 Laravel 框架的基础服务提供者
- registerCoreContainerAliases():将具体的「依赖注入容器」及其别名注册到「Laravel 服务容器」
正因为registerBaseBindings将App实例将自身注册到「服务容器」,我们才可以使用 $this->app->make(‘xxx’) 去解析一项服务。
注册基础服务提供者
/**
* Register all of the base service providers.
*
* @return void
*/
protected function registerBaseServiceProviders()
{
$this->register(new EventServiceProvider($this));
$this->register(new LogServiceProvider($this));
$this->register(new RoutingServiceProvider($this));
}
...
/**
* Register a service provider with the application.
*
* @param \Illuminate\Support\ServiceProvider|string $provider
* @param bool $force
* @return \Illuminate\Support\ServiceProvider
*/
public function register($provider, $force = false)
{
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.
if (is_string($provider)) {
$provider = $this->resolveProvider($provider);
}
$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.
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.
执行服务提供者 boot 方法启动程序
if ($this->isBooted()) {
$this->bootProvider($provider);
}
return $provider;
}
...
/**
* Boot the given service provider.
* 启动给定服务提供者
* @param \Illuminate\Support\ServiceProvider $provider
* @return mixed
*/
protected function bootProvider(ServiceProvider $provider)
{
if (method_exists($provider, 'boot')) {
return $this->call([$provider, 'boot']);
}
}
我们可以发现在registerBaseServiceProviders方法中进行注册了三个服务提供者,分别是EventServiceProvider、LogServiceProvider、RoutingServiceProvider。
Laravel 服务容器在执行注册方法时,需要进行如下处理:
- 将服务实现绑定到容器操作 $provider->register()
- 如果服务提供者存在 boot 方法,会在 bootProvider 方法内执行启动方法来启动这个服务
我们可以打开LogServiceProvider看一下,在LogServiceProvider的register方法中,仅仅只是实现将闭包绑定到服务容器。
namespace Illuminate\Log;
use Illuminate\Support\ServiceProvider;
class LogServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->app->singleton('log', function () {
return new LogManager($this->app);
});
}
}
注册服务别名到服务容器
通过Laravel Facades 和别名系统,开发者可以简单的使用服务容器中的各种服务,而注册别名和对应服务的映射关系,在registerCoreContainerAliases方法中完成,对于Facades的详情,有兴趣的可以看看我的这篇《聊一聊Laravel的核心概念----Facades》,在这里就不细说了。
管理所需创建的类及其依赖
对于服务容器来说,其bind、singleton等各种绑定方法,它们的原理是类似的。绑定方法定义在Illuminate\Foundation\Application类中,通过上面的代码,我们知道Application类继承的是Illuminate\Container\Container类,这些与服务容器绑定相关的方法便直接继承自 Container 类。
bind方法绑定原理
我们可以看一下Container类的bind方法:
/**
* 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)
{
// 如果未提供实现类 $concrete,我们直接将抽象类作为实现 $abstract。
// 这之后,我们无需明确指定 $abstract 和 $concrete 是否为单例模式,
// 而是通过 $shared 标识来决定它们是单例还是每次都需要实例化处理。
$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.
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.
// 如果绑定时传入的实现类非闭包,即绑定时是直接给定了实现类的类名,
// 这时要稍微处理下将类名封装成一个闭包,保证解析时处理手法的统一
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.
// 最后如果抽象类已经被容器解析过,我们将触发 rebound 监听器。
// 并且通过触发 rebound 监听器回调,将任何已被解析过的服务更新最新的实现到抽象接口
if ($this->resolved($abstract)) {
$this->rebound($abstract);
}
}
/**
* Drop all of the stale instances and aliases.
* 删除所有过时的实例和别名。
* @param string $abstract
* @return void
*/
protected function dropStaleInstances($abstract)
{
unset($this->instances[$abstract], $this->aliases[$abstract]);
}
/**
* 获取在构建类型时要使用的闭包
* 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->resolve(
$concrete, $parameters, $raiseEvents = false
);
};
}
...
/**
* Fire the "rebound" callbacks for the given abstract type.
* 依据给定的抽象服务接口,触发其 "rebound" 回调
* @param string $abstract
* @return void
*/
protected function rebound($abstract)
{
$instance = $this->make($abstract);
foreach ($this->getReboundCallbacks($abstract) as $callback) {
call_user_func($callback, $this, $instance);
}
}
/**
* Get the rebound callbacks for a given type.
* 获取给定抽象服务的回调函数。
* @param string $abstract
* @return array
*/
protected function getReboundCallbacks($abstract)
{
return $this->reboundCallbacks[$abstract] ?? [];
}
在bind方法中,完成了几个方面的处理:
- 注销解析过的服务实例;
- 将绑定的实现类封装成闭包,以确保后续处理的统一;
- 针对已解析过的服务实例,再次触发重新绑定回调函数,同时将最新的实现类更新到接口里面。
在绑定过程中,服务容器并不会执行服务的解析操作,这样有利于提升服务的性能。直到在项目运行期间,被使用时才会真正解析出需要使用的对应服务,实现「按需加载」。
make解析处理
make函数的声明和实现和bind方法一样都是在 Illuminate\Container\Container 类中,服务容器的解析无论是通过make等方法的手动解析还是自动注入,其实现原理都是通过PHP的反射机制
/**
* Resolve the given type from the container.
* 从容器中解析出给定服务具体实现
* @param string $abstract
* @param array $parameters
* @return mixed
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
public function make($abstract, array $parameters = [])
{
return $this->resolve($abstract, $parameters);
}
...
* Resolve the given type from the container.
* 从容器中解析出给定服务具体实现
* @param string $abstract
* @param array $parameters
* @param bool $raiseEvents
* @return mixed
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
protected function resolve($abstract, $parameters = [], $raiseEvents = true)
{
$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.
// 如果给定的类型已单例模式绑定,直接从服务容器中返回这个实例而无需重新实例化
if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
return $this->instances[$abstract];
}
$this->with[] = $parameters;
$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.
// 已准备就绪创建这个绑定的实例。下面将实例化给定实例及内嵌的所有依赖实例。
// 到这里我们已经做好创建实例的准备工作。只有可以构建的服务才可以执行 build 方法去实例化服务;
// 否则也就是说我们的服务还存在依赖,然后不断的去解析嵌套的依赖,知道它们可以去构建(isBuildable)。
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.
// 如果我们的服务存在扩展(extend)绑定,此时就需要去执行扩展。
// 扩展绑定适用于修改服务的配置或者修饰(decorating)服务实现。
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),
// 后续便可以直接获取单例服务对象了。
if ($this->isShared($abstract) && ! $needsContextualBuild) {
$this->instances[$abstract] = $object;
}
if ($raiseEvents) {
$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;
}
...
/**
* Determine if the given concrete is buildable.
*
* @param mixed $concrete
* @param string $abstract
* @return bool
*/
protected function isBuildable($concrete, $abstract)
{
// 仅当实现类和接口相同或者实现为闭包时可构建
return $concrete === $abstract || $concrete instanceof Closure;
}
...
/**
* Instantiate a concrete instance of the given type.
* 构建(实例化)给定类型的实现类(匿名函数)实例
* @param string $concrete
* @return mixed
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
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());
}
// 使用PHP的反射
$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);
}
$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;
}
// 获取到实现类构造函数依赖参数
$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.
try {
// 尝试解析出所有依赖
$instances = $this->resolveDependencies($dependencies);
} catch (BindingResolutionException $e) {
array_pop($this->buildStack);
throw $e;
}
array_pop($this->buildStack);
// 这是我们就可以创建服务实例并返回。
return $reflector->newInstanceArgs($instances);
}
...
/**
* Resolve all of the dependencies from the ReflectionParameters.
* 从 ReflectionParameters 解析出所有构造函数所需依赖
* @param array $dependencies
* @return array
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
protected function resolveDependencies(array $dependencies)
{
$results = [];
foreach ($dependencies as $dependency) {
// If this dependency has a override for this particular build we will use
// that instead as the value. Otherwise, we will continue with this run
// of resolutions and let reflection attempt to determine the result.
//如果这个依赖项对这个特定的构建有一个覆盖,我们将使用它作为值。否则,我们将继续执行这一系列的解决方案,让反射尝试确定结果。
if ($this->hasParameterOverride($dependency)) {
$results[] = $this->getParameterOverride($dependency);
continue;
}
// If the class is null, it means the dependency is a string or some other
// primitive type which we can not resolve since it is not a class and
// we will just bomb out with an error since we have no-where to go.
// 构造函数参数为非类时,即参数为 string、int 等标量类型或闭包时,按照标量和闭包解析;
// 否则需要解析类。
$results[] = is_null($dependency->getClass())
? $this->resolvePrimitive($dependency)
: $this->resolveClass($dependency);
}
return $results;
}
/**
* Resolve a non-class hinted primitive dependency.
* 依据类型提示解析出 标量/闭包 类型数据
* @param \ReflectionParameter $parameter
* @return mixed
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
protected function resolvePrimitive(ReflectionParameter $parameter)
{
if (! is_null($concrete = $this->getContextualConcrete('$'.$parameter->name))) {
return $concrete instanceof Closure ? $concrete($this) : $concrete;
}
if ($parameter->isDefaultValueAvailable()) {
return $parameter->getDefaultValue();
}
$this->unresolvablePrimitive($parameter);
}
/**
* Resolve a class based dependency from the container.
* 从服务容器中解析出类依赖(自动注入)
* @param \ReflectionParameter $parameter
* @return mixed
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
protected function resolveClass(ReflectionParameter $parameter)
{
try {
return $this->make($parameter->getClass()->name);
}
// If we can not resolve the class instance, we will check to see if the value
// is optional, and if it is we will return the optional parameter value as
// the value of the dependency, similarly to how we do this with scalars.
catch (BindingResolutionException $e) {
if ($parameter->isOptional()) {
return $parameter->getDefaultValue();
}
throw $e;
}
}
这就是make的解析原理,通过PHP的反射机制,实现了自动依赖注入和服务解析:
- 对于单例绑定数据,如果解析过服务则直接返回,否则继续执行解析
- 非单例绑定的服务类型,通过接口获取绑定实现类
- 接口即服务或者闭包时进行构建(build)处理,构建时依托于 PHP 反射机制进行自动依赖注入解析出完整的服务实例对象;否则继续解析(make)出所有嵌套的依赖
- 如果服务存在扩展绑定,解析出扩展绑定结果
- 如果绑定服务为单例绑定类型(singleton),将解析到的服务加入到单例对象池
- 其它处理如触发绑定监听器、将服务标记为已解析状态等,并返回服务实例
以上的流程就是服务容器的部分实现原理,我在浏览代码时只是比较粗的看,如果有兴趣,可以详细浏览。
参考:
http://blog.phpzendo.com/?p=353
https://laravelacademy.org/post/19434.html