Open xiaohuilam opened 6 years ago
容器的 singleton 和 bind 方法在整个 Laravel 框架或扩展中是调用的比较频繁的底层方法,掌握其原理能帮助我们加深 laravel 容器的理解。
singleton
bind
singleton()
单身狗模式
https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L338-L348
bind()
分裂模式
https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L206-L240
初略看二者差异可能看不大出来。 我们从他们暴露的接口入手,先用,再啪(扒)源码。
祭出大杀器:php artisan tinker
php artisan tinker
>>> app()->bind('test', function(){dump(1); return new \StdClass();}); 1 => null >>> >>> app()->make('test') 1 => {#2895} >>> >>> app()->make('test') 1 => {#2896}
可以看出,调用 bind 时,每次取出被绑定的对象时,都会重新去构建一次。
如果用singleton 呢。
>>> app()->singleton('test', function(){dump(1); return new \StdClass();}); 1 => null >>> app()->make('test') => {#2915} >>> app()->make('test') => {#2915} >>>
我们回头再看下 bind() 方法 https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L206-L240
逻辑步骤为:
$abstract
$concrete
dropStaleInstances
$concret
Container::getClosure()
$container
$parameters
Container::$bindings
compact('concrete', 'shared')
["concrete" => $concrete, "shared" => $shared]
resolved
Container::rebound()
reboundCallbacks
到目前为止,暂时还没看出 $share 对 singleton 和 bind 的影响。只知道 bind 在执行过程中,将 $shared 和 $concrete 一起存到了 Container::$bindings 中。
$share
$shared
用句四川话,莫慌,我们马上就要揭开关键的面纱了。。。
Container::make()
https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L592-L602
其实 make 是直接透传给了 resolve https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L616-L675 第一步骤的 https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L625 是做了森么呢? 还原用 Container::alias() 方法设置过别名的原始 $abstract。 下一步, https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L627-L629 Container::getContextualConcrete() 其实是取这里设置进取的 上下文绑定 的数据 https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/ContextualBindingBuilder.php#L56-L67 ContextualBindingBuilder::give() 暂时就不在这里暂开研究了。咱们继续往下看 $needsContextualBuild 值为 make() 方法是否传了第二个参数,也就是 $parameters。 needs contextual build 意思是 构建时,是否具有上下文关联性 https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L634-L636 判断 Container::$instances 数组中是否能通过 $abstract 找到对象。如果找得到且不具有上下文关联性,就用之前创建好的对象。 https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L638-L640 getConcrete() 代码为 https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L680-L697 相信你能看出来 return $this->bindings[$abstract]['concrete'] ,就是根据 $abstract 把前面 bind 或 singleton 的数据返回。 这里的 isBuildable 是判断 $abstract 是否与 $concrete 一致或 $concrete 为闭包 如果不是,那么可能是还有递归调用,需要再走一次 make https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L645-L649 645 行调用的 isBuildable() https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L745-L748 接着调用了 Container::build() 方法 https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L758-L801 其中的 https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L767 和 https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L789-L800 是通过反射解析待构建类所需参数中其他对象参数,从容器中取出填入。 再往下面就是执行 Container::extend 设置的扩充流程了。 https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L654-L656 再后面,才是对我们 singleton() 和 bind() 产生深远影响的代码: https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L661-L663 我们在前面知道,如果为 singleton() 执行到 resolve() 的第二个参数 $shared 为 true, bind 为 false。 前面 $needsContextualBuild 判断的位置,判断了 Container::$instance 中是否有 $abstract, 所以在这里如果 resolve 没有将 $object 绑入 Container::$instance,那么下次 Container::make() 依旧会在去 build() 一次,这就产生了 singleton() 和 bind()的特性。 再接着,就是触发事件了。 https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L665 最后是一些收尾工作 https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L670-L674 此章节为什么叫做单身狗呢?因为 Container::make() 严格遵守 $shared,如果声明了 true,他保证不搞一个分身(所以是单身)给你。
其实 make 是直接透传给了 resolve
make
resolve
https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L616-L675
第一步骤的 https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L625 是做了森么呢? 还原用 Container::alias() 方法设置过别名的原始 $abstract。
Container::alias()
下一步, https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L627-L629
Container::getContextualConcrete() 其实是取这里设置进取的 上下文绑定 的数据 https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/ContextualBindingBuilder.php#L56-L67
Container::getContextualConcrete()
上下文绑定
ContextualBindingBuilder::give() 暂时就不在这里暂开研究了。咱们继续往下看
ContextualBindingBuilder::give()
$needsContextualBuild 值为 make() 方法是否传了第二个参数,也就是 $parameters。 needs contextual build 意思是
$needsContextualBuild
make()
needs contextual build
构建时,是否具有上下文关联性 https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L634-L636 判断 Container::$instances 数组中是否能通过 $abstract 找到对象。如果找得到且不具有上下文关联性,就用之前创建好的对象。 https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L638-L640 getConcrete() 代码为 https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L680-L697 相信你能看出来 return $this->bindings[$abstract]['concrete'] ,就是根据 $abstract 把前面 bind 或 singleton 的数据返回。
构建时,是否具有上下文关联性
https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L634-L636 判断 Container::$instances 数组中是否能通过 $abstract 找到对象。如果找得到且不具有上下文关联性,就用之前创建好的对象。
Container::$instances
https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L638-L640
getConcrete() 代码为 https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L680-L697
getConcrete()
相信你能看出来 return $this->bindings[$abstract]['concrete'] ,就是根据 $abstract 把前面 bind 或 singleton 的数据返回。
return $this->bindings[$abstract]['concrete']
这里的 isBuildable 是判断 $abstract 是否与 $concrete 一致或 $concrete 为闭包
isBuildable
如果不是,那么可能是还有递归调用,需要再走一次 make
https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L645-L649
645 行调用的 isBuildable() https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L745-L748 接着调用了 Container::build() 方法 https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L758-L801 其中的 https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L767 和 https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L789-L800 是通过反射解析待构建类所需参数中其他对象参数,从容器中取出填入。
645 行调用的 isBuildable() https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L745-L748
isBuildable()
接着调用了 Container::build() 方法 https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L758-L801
Container::build()
其中的 https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L767 和 https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L789-L800 是通过反射解析待构建类所需参数中其他对象参数,从容器中取出填入。
再往下面就是执行 Container::extend 设置的扩充流程了。 https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L654-L656
Container::extend
再后面,才是对我们 singleton() 和 bind() 产生深远影响的代码: https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L661-L663
我们在前面知道,如果为 singleton() 执行到 resolve() 的第二个参数 $shared 为 true, bind 为 false。 前面 $needsContextualBuild 判断的位置,判断了 Container::$instance 中是否有 $abstract, 所以在这里如果 resolve 没有将 $object 绑入 Container::$instance,那么下次 Container::make() 依旧会在去 build() 一次,这就产生了 singleton() 和 bind()的特性。
resolve()
true
false
Container::$instance
$object
build()
再接着,就是触发事件了。 https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L665
最后是一些收尾工作 https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L670-L674
此章节为什么叫做单身狗呢?因为 Container::make() 严格遵守 $shared,如果声明了 true,他保证不搞一个分身(所以是单身)给你。
bind的分析,'如果为闭包' 应该是 '如果不是闭包' 吧?因为程序是 if (! $concrete instanceof Closure)
是的,打错了
初识
singleton()
代码https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L338-L348
bind()
代码https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L206-L240
初略看二者差异可能看不大出来。 我们从他们暴露的接口入手,先用,再啪(扒)源码。
调试
祭出大杀器:
php artisan tinker
bind
可以看出,调用
bind
时,每次取出被绑定的对象时,都会重新去构建一次。如果用
singleton
呢。singleton
分析
bind 的实质,容器中捆绑
我们回头再看下
bind()
方法 https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L206-L240逻辑步骤为:
$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()
神秘的面纱https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L592-L602