10. 容器的 singleton 和 bind 的实现
xiaohuilam opened this issue · comments
容器的
singleton
和bind
方法在整个 Laravel 框架或扩展中是调用的比较频繁的底层方法,掌握其原理能帮助我们加深 laravel 容器的理解。
初识
singleton()
代码
单身狗模式
laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php
Lines 338 to 348 in d081c91
bind()
代码
分裂模式
laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php
Lines 206 to 240 in d081c91
初略看二者差异可能看不大出来。
我们从他们暴露的接口入手,先用,再啪(扒)源码。
调试
祭出大杀器: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()
方法
laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php
Lines 206 to 240 in d081c91
逻辑步骤为:
- 参数依次为
$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()
神秘的面纱
laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php
Lines 592 to 602 in d081c91
其实
make
是直接透传给了resolve
laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php
Lines 616 to 675 in d081c91
第一步骤的
是做了森么呢?
还原用Container::alias()
方法设置过别名的原始$abstract
。下一步,
laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php
Lines 627 to 629 in d081c91
Container::getContextualConcrete()
其实是取这里设置进取的上下文绑定
的数据
ContextualBindingBuilder::give()
暂时就不在这里暂开研究了。咱们继续往下看
$needsContextualBuild
值为make()
方法是否传了第二个参数,也就是$parameters
。needs contextual build
意思是构建时,是否具有上下文关联性
laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php
Lines 634 to 636 in d081c91
判断Container::$instances
数组中是否能通过$abstract
找到对象。如果找得到且不具有上下文关联性,就用之前创建好的对象。laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php
Lines 638 to 640 in d081c91
getConcrete()
代码为
laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php
Lines 680 to 697 in d081c91
相信你能看出来
return $this->bindings[$abstract]['concrete']
,就是根据$abstract
把前面bind
或singleton
的数据返回。这里的
isBuildable
是判断$abstract
是否与$concrete
一致或$concrete
为闭包如果不是,那么可能是还有递归调用,需要再走一次
make
laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php
Lines 645 to 649 in d081c91
645 行调用的
isBuildable()
laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php
Lines 745 to 748 in d081c91
接着调用了
Container::build()
方法laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php
Lines 758 to 801 in d081c91
其中的
和是通过反射解析待构建类所需参数中其他对象参数,从容器中取出填入。laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php
Lines 789 to 800 in d081c91
再往下面就是执行
Container::extend
设置的扩充流程了。
laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php
Lines 654 to 656 in d081c91
再后面,才是对我们
singleton()
和bind()
产生深远影响的代码:
laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php
Lines 661 to 663 in d081c91
我们在前面知道,如果为
singleton()
执行到resolve()
的第二个参数$shared
为true
,bind
为false
。
前面$needsContextualBuild
判断的位置,判断了Container::$instance
中是否有$abstract
, 所以在这里如果resolve
没有将$object
绑入Container::$instance
,那么下次Container::make()
依旧会在去build()
一次,这就产生了singleton()
和bind()
的特性。再接着,就是触发事件了。
最后是一些收尾工作
laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php
Lines 670 to 674 in d081c91
此章节为什么叫做单身狗呢?因为
Container::make()
严格遵守$shared
,如果声明了true
,他保证不搞一个分身(所以是单身)给你。
bind的分析,'如果为闭包' 应该是 '如果不是闭包' 吧?因为程序是 if (! $concrete instanceof Closure)
bind的分析,'如果为闭包' 应该是 '如果不是闭包' 吧?因为程序是 if (! $concrete instanceof Closure)
是的,打错了