xiaohuilam / laravel

Laravel 深入详解 —— 源代码解析,新手进阶指南
433 stars 80 forks source link

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

Open xiaohuilam opened 6 years ago

xiaohuilam commented 6 years ago

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

初识

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

>>> 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() 方法 https://github.com/xiaohuilam/laravel/blob/d081c918b7e582ec5b3f94316f44834466cec37d/vendor/laravel/framework/src/Illuminate/Container/Container.php#L206-L240

逻辑步骤为:


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

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

揭开 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() 方法是否传了第二个参数,也就是 $parametersneeds 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 把前面 bindsingleton 的数据返回。

这里的 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#L767https://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() 的第二个参数 $sharedtrue, bindfalse。 前面 $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,他保证不搞一个分身(所以是单身)给你。

HubQin commented 5 years ago

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

xiaohuilam commented 5 years ago

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

是的,打错了