理解了前面部分的机制后,我们开始来阅读源码
首先从 Bind 绑定开始。
Illuminate\Container, Container 最重要的方法:bind
绑定分为几种:
bind 把接口和其实现类绑定,当 make 解析接口的时候创建其实现类的实例对象。
single 把接口和其实现类绑定,当第一次 make 解析的时候创建实例,后面都返回该实例不再创建。
instance 把接口和其实现类的实例绑定,直接绑定实例对象。
上下文绑定
自动绑定
tag 绑定
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 的值。
方法体:
- 绑定前,先清空 instances 和 aliases 中存在的同名字的服务。
$this->dropStaleInstances($abstract);
1.1.dropStaleInstances($abstract) 如下,就是清空当前 instance 中和 aliases 中存在的 $abstruct 同名的服务。
protected function dropStaleInstances($abstract)
{
unset($this->instances[$abstract], $this->aliases[$abstract]);
}
- 然后判断第二个参数 $concrete 是不是空,如果是空,就视 $abstruct 和 $concrete 一样。比如: app()->bind(Boss::class)。
if (is_null($concrete))
{
$concrete = $abstract;
}
- 如果当前这个 $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( c o n t a i n e r , container, container,parameters=[])。赋值给变量 $concrete。而这个闭包内返回的是通过 build 或者 make 解析的值
- 我们回到 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的第三个参数
]
- 接下来下一句,如果当前的抽象类曾经被解析过。那再次绑定的时候,我们要使用 rebound 函数触发 reboundCallbacks 数组中的回调函数。
关于回调函数参看前面章节
if ($this->resolved($abstract))
{
$this->rebound($abstract);
}
如何判断当前的 $abstruct 曾经被解析过呢,我们看下 resolved 函数。两个条件
-
简单判断当前 resolved 数组中是否存在 $abstruct。
-
或者 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 方法中。
- 首先移除旧的实例,如果参数 $concrete 不是闭包,是类名,会通过 getClosure 函数将类名封装进闭包中,返回这个闭包。总之 container 就要闭包。
注意:build 和 make 都是在一个闭包函数中,闭包函数不触发,它是不会创建对象的。也就是所谓的懒加载。关于 build 和 make 他是如果操作的,下几章讲解。
-
然后把返回的闭包函数和 share 的值组合放入 $this->bindings 数组中。
-
最后判断当前这个 $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();});
————————————————
原文作者:HarveyNorman
转自链接:https://learnku.com/articles/41504
版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请保留以上作者信息和原文链接。