容器的 singleton
和 bind
方法在整个 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
中的回调。
到目前为止,暂时还没看出 $share
对 singleton
和 bind
的影响。只知道 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()
方法是否传了第二个参数,也就是 $parameters
。 needs 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
把前面 bind
或 singleton
的数据返回。
这里的 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()
的第二个参数 $shared
为 true
, bind
为 false
。
前面 $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
,他保证不搞一个分身(所以是单身)给你。