Container tag解析机制

思考并回答以下问题:

简介

先说作用,给某一类的绑定分配一个标记来表示他们是一类的,可以通过这个标记取出同一类下面的所有的绑定。

看看怎么用就知道了:

1
2
3
4
5
6
7
8
9
10
11
12
// 定义两个实体类,都标记名字为currency
// 然后使用tagged取出currency就是可以取出abstract的实体对象。
public function testTags()
{
$this->app->tag(Rmb::class, 'currency');
$this->app->tag(Dollar::class, 'currency');

$currencyArray = $this->app->tagged('currency');

$this->assertTrue($currencyArray[0] instanceof Rmb);
$this->assertTrue($currencyArray[1] instanceof Dollar);
}

注意这里:我们绑定的时候还是一个abstract,就是说容器中还没有对象,但是我们tagged取出的时候都变成对象了。因为make可以直接解析类路径,而tagged使用了make方法。

实例测试

测试提供类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class AusDollars
{
private $amount;

public function __construct($amount)
{
$this->amount = $amount;
}
}

class Rmb
{

}

class Dollar
{
public function __construct()
{
}

public function getAmount()
{
return 1;
}
}

Class Currency
{
private $dollar;

public function __construct(Dollar $dollar)
{
$this->dollar = $dollar;
}

public function getAmount()
{
return $this->dollar->getAmount();
}
}

1.测试多个参数情况,以及直接使用类路径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public function testTags()
{
$this->app->bind('rmb', Rmb::class);

// 多个参数
$this->app->tag('rmb', 'currency', 'money');

// 直接使用类路径
$this->app->tag(Dollar::class, 'currency', 'money');

$currencyArray = $this->app->tagged('money');

$this->assertTrue($currencyArray[0] instanceof Rmb);
$this->assertTrue($currencyArray[1] instanceof Dollar);
}

2.测试使用tag直接绑定有依赖的类路径,会报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
public function testTagsWithDependency()
{
$this->app->tag(AusDollars::class, 'currency', 'money');

try
{
$this->app->tagged('money');
}
catch(\Exception $e)
{
$this->assertContains('Unresolvable dependency resolving',$e->getMessage());
}
}

会报这样的错:

1
`Unresolvable dependency resolving [Parameter #0 [ <required> $amount ]] in class ...`

源码

1.tag方法源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* Assign a set of tags to a given binding.
*
* @param array|string $abstracts
* @param array|mixed ...$tags
* @return void
*/
public function tag($abstracts, $tags)
{
$tags = is_array($tags) ? $tags : array_slice(func_get_args(), 1);

foreach ($tags as $tag)
{
if (! isset($this->tags[$tag]))
{
$this->tags[$tag] = [];
}

foreach ((array) $abstracts as $abstract)
{
$this->tags[$tag][] = $abstract;
}
}
}

1.0 还是先说参数,传入的$abstracts可以是类路径,也可以是一个绑定的别名或者字符串。如果是后者,需要事先使用bind方法绑定对应的concrete。如果是类路径则可以直接使用,参见前面make方法。

1.1 判断第二个参数$tags是不是一个数组,如果不是数组,他可以是一个参数。

这里可以看到,这里也可以传入多个参数。array_slice会把第二个参数,以及后面所有的参数都转换成一个数组返回。

1
$tags = is_array($tags) ? $tags : array_slice(func_get_args(), 1);

什么意思呢,举例:

1
2
3
4
5
6
// 可以这样
$this->app->tag(Rmb::class, 'currency');
// 也可以传入数组
$this->app->tag(Rmb::class, ['currency', 'money']);
// 也可以这样
$this->app->tag(Rmb::class, 'currency',‘money’);

最后一种,我们看源码就知道可以这样使用,虽然我感觉可能不是一个好的写法。

1.2 下面就简单了,主要分两步:

  • a.遍历这个tags数组,如果在tags数组(protected $tags = [];)中不存在这个tag那么创建一个空的子数组。
  • b.遍历前面的第一个参数$abstracts,他会强行转换成一个数组,说明$abstract可以是一个字符串,也可以是一个数组。把当前的abstract对应的值存储到上面我们创建的子数组tag中。

这里我们同时知道了tags数组作用以及存储格式。

1
2
3
4
5
6
7
8
9
10
11
12
foreach ($tags as $tag) 
{
if (! isset($this->tags[$tag]))
{
$this->tags[$tag] = [];
}

foreach ((array) $abstracts as $abstract)
{
$this->tags[$tag][] = $abstract;
}
}

这是tags的存储过程。

2.tagged方法源代码,看看以tag标记的一类绑定如何获取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* Resolve all of the bindings for a given tag.
*
* @param string $tag
* @return iterable
*/
public function tagged($tag)
{
if (! isset($this->tags[$tag]))
{
return [];
}

return new RewindableGenerator(function () use ($tag) {
foreach ($this->tags[$tag] as $abstract)
{
yield $this->make($abstract);
}
}, count($this->tags[$tag]));
}

2.1 其实很简单,也是两个逻辑:

  • a.先判断当前容器的tags数组中有没有对应的值,如果没有直接返回空数组。
  • b.如果有,遍历这个子tag数组,分别使用make函数解析这个$abstract。

这里我们就能明白,为什么我们可以不用事先绑定类路径,而直接使用就能绑定成功,因为我们知道make解析的时候,如果在binding数组中找不到对应的值,他会使用build函数直接解析。

但是我们,也看到,这个make解析的时候是没有第二个参数parameters的,说明什么呢,如果这个类路径有自定义的依赖并且这个依赖没有默认值,它是无法实例化的。因为make他需要我们传入第二个参数才能实例化。

0%