Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

10. 容器的 singleton 和 bind 的实现 #10

Open
xiaohuilam opened this issue Sep 27, 2018 · 2 comments
Open

10. 容器的 singleton 和 bind 的实现 #10

xiaohuilam opened this issue Sep 27, 2018 · 2 comments
Labels
book The digital book for laravel learning service provider The book which talk about service providers

Comments

@xiaohuilam
Copy link
Owner

xiaohuilam commented Sep 27, 2018

容器的 singletonbind 方法在整个 Laravel 框架或扩展中是调用的比较频繁的底层方法,掌握其原理能帮助我们加深 laravel 容器的理解。

初识

singleton() 代码

单身狗模式

/**
* Register a shared binding in the container.
*
* @param string $abstract
* @param \Closure|string|null $concrete
* @return void
*/
public function singleton($abstract, $concrete = null)
{
$this->bind($abstract, $concrete, true);
}

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)
{
// 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.
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);
}
}

初略看二者差异可能看不大出来。
我们从他们暴露的接口入手,先用,再啪(扒)源码。

调试

祭出大杀器:php artisan tinker

bind

>>> app()->bind('test', function(){dump(1); return new \StdClass();});
1
=> null
>>>
>>> app()->make('test')
1
=> {#2895}
>>>
>>> app()->make('test')
1
=> {#2896}

可以看出,调用 bind 时,每次取出被绑定的对象时,都会重新去构建一次。

如果用singleton 呢。

singleton

>>> app()->singleton('test', function(){dump(1); return new \StdClass();});
1
=> null
>>> app()->make('test')
=> {#2915}
>>> app()->make('test')
=> {#2915}
>>>

分析

bind 的实质,容器中捆绑

我们回头再看下 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)
{
// 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.
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);
}
}

逻辑步骤为:

  • 参数依次为 $abstract$concrete
  • dropStaleInstances 移除之前 bind 过的 $abstract 的数据(旧的 $concret
  • 如果没有传 $concrete,则将 $concrete 设置成 $abstract
  • 如果为闭包 如果不为闭包(感谢 @HubQin 的指正),则通过 Container::getClosure() 方法将闭包改成一个接受 $container$parameters 的包裹后的闭包。此闭包实质就是在需要运行的时候,运行闭包。
  • Container::$bindings 数组 $abstract 为 key 的值设置为 compact('concrete', 'shared'),即 ["concrete" => $concrete, "shared" => $shared]
  • 如果 bind 的对象已经 resolved 过了,通过 Container::rebound() 触发 reboundCallbacks 中的回调。

到目前为止,暂时还没看出 $sharesingletonbind 的影响。只知道 bind 在执行过程中,将 $shared$concrete 一起存到了 Container::$bindings 中。

用句四川话,莫慌,我们马上就要揭开关键的面纱了。。。

揭开 Container::make() 神秘的面纱

/**
* Resolve the given type from the container.
*
* @param string $abstract
* @param array $parameters
* @return mixed
*/
public function make($abstract, array $parameters = [])
{
return $this->resolve($abstract, $parameters);
}

其实 make 是直接透传给了 resolve

/**
* Resolve the given type from the container.
*
* @param string $abstract
* @param array $parameters
* @return mixed
*/
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.
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.
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.
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;
}

第一步骤的

$abstract = $this->getAlias($abstract);

是做了森么呢?
还原用 Container::alias() 方法设置过别名的原始 $abstract

下一步,

$needsContextualBuild = ! empty($parameters) || ! is_null(
$this->getContextualConcrete($abstract)
);

Container::getContextualConcrete() 其实是取这里设置进取的 上下文绑定 的数据

/**
* Define the implementation for the contextual binding.
*
* @param \Closure|string $implementation
* @return void
*/
public function give($implementation)
{
$this->container->addContextualBinding(
$this->concrete, $this->needs, $implementation
);
}

ContextualBindingBuilder::give() 暂时就不在这里暂开研究了。咱们继续往下看

$needsContextualBuild 值为 make() 方法是否传了第二个参数,也就是 $parametersneeds contextual build 意思是

构建时,是否具有上下文关联性

if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
return $this->instances[$abstract];
}

判断 Container::$instances 数组中是否能通过 $abstract 找到对象。如果找得到且不具有上下文关联性,就用之前创建好的对象。

$this->with[] = $parameters;
$concrete = $this->getConcrete($abstract);

getConcrete() 代码为

* @param string $abstract
* @return mixed $concrete
*/
protected function getConcrete($abstract)
{
if (! is_null($concrete = $this->getContextualConcrete($abstract))) {
return $concrete;
}
// If we don't have a registered resolver or concrete for the type, we'll just
// assume each type is a concrete name and will attempt to resolve it as is
// since the container should be able to resolve concretes automatically.
if (isset($this->bindings[$abstract])) {
return $this->bindings[$abstract]['concrete'];
}
return $abstract;
}

相信你能看出来 return $this->bindings[$abstract]['concrete'] ,就是根据 $abstract 把前面 bindsingleton 的数据返回。

这里的 isBuildable 是判断 $abstract 是否与 $concrete 一致或 $concrete 为闭包

如果不是,那么可能是还有递归调用,需要再走一次 make

if ($this->isBuildable($concrete, $abstract)) {
$object = $this->build($concrete);
} else {
$object = $this->make($concrete);
}

645 行调用的 isBuildable()

protected function isBuildable($concrete, $abstract)
{
return $concrete === $abstract || $concrete instanceof Closure;
}

接着调用了 Container::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 of 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.
$instances = $this->resolveDependencies(
$dependencies
);
array_pop($this->buildStack);
return $reflector->newInstanceArgs($instances);
}

其中的

$reflector = new ReflectionClass($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.
$instances = $this->resolveDependencies(
$dependencies
);
array_pop($this->buildStack);
return $reflector->newInstanceArgs($instances);
是通过反射解析待构建类所需参数中其他对象参数,从容器中取出填入。

再往下面就是执行 Container::extend 设置的扩充流程了。

foreach ($this->getExtenders($abstract) as $extender) {
$object = $extender($object, $this);
}

再后面,才是对我们 singleton()bind() 产生深远影响的代码:

if ($this->isShared($abstract) && ! $needsContextualBuild) {
$this->instances[$abstract] = $object;
}

我们在前面知道,如果为 singleton() 执行到 resolve() 的第二个参数 $sharedtrue, bindfalse
前面 $needsContextualBuild 判断的位置,判断了 Container::$instance 中是否有 $abstract, 所以在这里如果 resolve 没有将 $object 绑入 Container::$instance,那么下次 Container::make() 依旧会在去 build() 一次,这就产生了 singleton()bind()的特性。

再接着,就是触发事件了。

$this->fireResolvingCallbacks($abstract, $object);

最后是一些收尾工作

$this->resolved[$abstract] = true;
array_pop($this->with);
return $object;

此章节为什么叫做单身狗呢?因为 Container::make() 严格遵守 $shared,如果声明了 true,他保证不搞一个分身(所以是单身)给你。

@xiaohuilam xiaohuilam added the book The digital book for laravel learning label Sep 27, 2018
@xiaohuilam xiaohuilam changed the title 10. 容器的 make、singleton和bind [0%] 10. 容器的 singleton和bind [0%] Sep 27, 2018
@xiaohuilam xiaohuilam changed the title 10. 容器的 singleton和bind [0%] 10. 容器的 singleton和bind [30%] Sep 27, 2018
@xiaohuilam xiaohuilam changed the title 10. 容器的 singleton和bind [30%] 10. 容器的 singleton 和 bind 的实现 Sep 27, 2018
@xiaohuilam xiaohuilam added the service provider The book which talk about service providers label Oct 5, 2018
@HubQin
Copy link

HubQin commented Dec 7, 2018

bind的分析,'如果为闭包' 应该是 '如果不是闭包' 吧?因为程序是 if (! $concrete instanceof Closure)

@xiaohuilam
Copy link
Owner Author

bind的分析,'如果为闭包' 应该是 '如果不是闭包' 吧?因为程序是 if (! $concrete instanceof Closure)

是的,打错了

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
book The digital book for laravel learning service provider The book which talk about service providers
Projects
None yet
Development

No branches or pull requests

2 participants