1.6 - Laravel - 5.6 - Container Bind 机制
理解了前面部分的机制后,我们开始来阅读源码
首先从Bind绑定开始。
Illuminate\Container
, Container最重要的方法:bind
绑定分为几种:
1. bind 把接口和其实现类绑定,当make解析接口的时候创建其实现类的实例对象。
2. single 把接口和其实现类绑定,当第一次make解析的时候创建实例,后面都返回该实例不再创建。
3. instance 把接口和其实现类的实例绑定,直接绑定实例对象。
4. 上下文绑定
5. 自动绑定
6. tag绑定
7. extends扩展绑定
先大概看bind下源代码:
public function bind($abstract, $concrete = null, $shared = false)
{
$this->dropStaleInstances($abstract);
if (is_null($concrete)) {
$concrete = $abstract;
}
if (! $concrete instanceof Closure) {
$concrete = $this->getClosure($abstract, $concrete);
}
$this->bindings[$abstract] = compact('concrete', 'shared');
if ($this->resolved($abstract)) {
$this->rebound($abstract);
}
}
参数:
0.1 首先明确第一个参数 $abstruct
简单说就是id,可以当做是存入容器中的名字。他可以是一个字符串,一个类,甚至是一个接口。
0.2 第二个参数 $concrete 简单说就是真实的值,可以当做是一个真正存入容器的实体。他可以是一个实现类,实例,或者一个闭包(闭包可以返回一个实现类的实例)。
0.3 第三个参数控制Shared的值。
方法体:
1. 绑定前,先清空instances和aliases中存在的同名字的服务。
$this->dropStaleInstances($abstract);
1.1.dropStaleInstances($abstract)
如下,就是清空当前instance中和aliases中存在的$abstruct同名的服务。
protected function dropStaleInstances($abstract)
{
unset($this->instances[$abstract], $this->aliases[$abstract]);
}
2. 然后判断第二个参数$concrete
是不是空,如果是空,就视$abstruct
和$concrete
一样。比如: app()->bind(Boss::class)
。
if (is_null($concrete))
{
$concrete = $abstract;
}
3.如果当前这个$concrete
不是一个闭包。就调用getClosure,返回一个闭包便于后面的操作。
if (! $concrete instanceof Closure)
{
$concrete = $this->getClosure($abstract, $concrete);
}
3.1.我们去看看这个getClosure方法是怎么返回一个闭包的。
很简单代码最后直接返回了一个这样形式的闭包:function($container,$parameters=[])
。
一些细节:
使用use关键字调用父类就是getClosure传入的$abstruct
和$concrete
两个参数。
如果$abstruct
和$concrete
是一样的,就是如果只有一个参数,或者确实两个参数一样,像这样 app->bind(User::class,User::class)
,那么就调用build方法。
否则使用make方法。(build方法和make方法,参看后面章节)
protected function getClosure($abstract, $concrete)
{
return function ($container, $parameters = []) use ($abstract, $concrete) {
if ($abstract == $concrete) {
return $container->build($concrete);
}
return $container->make($concrete, $parameters);};
}
不管怎么样,代码最后直接返回了一个这样形式的闭包:function($container,$parameters=[])
。赋值给变量$concrete
。而这个闭包内返回的是通过build或者make解析的值
4.我们回到bind方法,上面$concrete
得到一个闭包函数后,调用compact把$concrete
和$shard
(第三个参数判断是否shared)组成一个key分别为concrete和shared的数组,存入binding数组中,而binding数组的key是当前的抽象类。
$this->bindings[$abstract] = compact('concrete', 'shared');
处理后结构是这样的:
$binding[$abstract] => [
'concrete' => function($container,$parameters=[]),//getClosure()得到的
'shared' => true/false,//shared的值是bind的第三个参数
]
5.接下来下一句,如果当前的抽象类曾经被解析过。那再次绑定的时候,我们要使用rebound函数触发reboundCallbacks数组中的回调函数。
关于回调函数参看前面章节
if ($this->resolved($abstract))
{
$this->rebound($abstract);
}
如何判断当前的$abstruct
曾经被解析过呢,我们看下resolved函数。两个条件
1.简单判断当前resolved数组中是否存在$abstruct
。
2.或者instances数组中是存在对应的值。但我们注意,先前在bind方法的第一句$this->dropStaleInstances($abstract);
的时候我们清空了instances对应的$abstruct
的值,所以这边主要是考虑$abstruct
的别名在instances中是否存在残留的情况。
public function resolved($abstract)
{
if ($this->isAlias($abstract)) {
$abstract = $this->getAlias($abstract);
}
return isset($this->resolved[$abstract]) ||
isset($this->instances[$abstract]);
}
总结:
在bind方法中。
1.首先移除旧的实例,如果参数$concrete
不是闭包,是类名,会通过getClosure函数将类名封装进闭包中,返回这个闭包。总之container就要闭包。
注意:build和make都是在一个闭包函数中,闭包函数不触发,它是不会创建对象的。也就是所谓的懒加载。关于build和make他是如果操作的,下几章讲解。
2.然后把返回的闭包函数和share的值组合放入$this->bindings数组中。
3.最后判断当前这个$abstruct是否以前被解析过,如果是,要触发对应的回调函数。
**最简单的来说
就是原来在容器中,绑定的是一个id和一个闭包函数的组合。你传入闭包最好,不是闭包,laravel会转成闭包存起来。
暂时从代码来看,我们可以猜想,最后从容器解析出来的对象是运行这个闭包产生返回的。
那我们就会有这样的猜想了,我们可以通过闭包绑定任何类型的值,因为只要在闭包中返回我们想要的任何类型的值就好了。**
实例测试
测试1:使用闭包函数返回有依赖的对象。
class Money
{
private $amount = 0;
public function __construct($amount)
{
$this->amount = $amount;
}
public function getAmount()
{
return $this->amount;
}
}
//注意闭包的形式
$this->app->bind('money', function($app, $parameters){return new Money($parameters[0]);});
测试2:使用闭包函数返回没有依赖的对象
class Dollar
{
public function getAmount(){
return 100;
}
}
$this->app->bind('dollar', function(){return new Dollar();});
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: