Container extend扩展机制

思考并回答以下问题:

简介

extend方法可以修改解析的服务。例如,当一个服务被解析后,你可以添加额外的代码去修饰或配置这个服务。extend方法接受一个闭包,闭包的唯一参数和返回值都是一个服务:

1
2
3
$this->app->extend(Service::class, function($service) {
return new DecoratedService($service);
});

extend主要的作用是在解析后,使用一个闭包函数产生的值(通常为当前实体类的子类)替换对应父实体类,从而对其产生扩展影响。这个闭包的参数和返回值都必须是对象。比如:

1
2
3
app()->extend('Service', function ($service, $app) {
return new DecoratedService($service);
});

这里DecoratedService是Service的子类。

更简单来说,当我们从Container容器中取出一个实例后,用这个类的子类实例替换掉当前这个父类实例,达到扩展的作用。

当然这是一种用法。还有一些细节,我们去看下源代码。

实测实例

实例1:替换依赖的实例对象

本来的Boss实例的依赖Money是一个Cheque对象,最后解析的时候,被替换成了Dollar对象。

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
interface Money
{
public function getAmount();
}

class Dollar implements Money
{
public function getAmount()
{
return 1;
}
}

class Cheque implements Money
{
public function getAmount()
{
return 100000;
}
}

class Worker
{
private $money;

public function __construct(Money $money)
{
$this->money = $money; // prints '1'
}

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

class Boss
{
private $money;

public function __construct(Money $money)
{
$this->money = $money; // prints '100000'
}
public function getA(){
return $this->money->getAmount();
}
}

public function testClosure()
{
app()->bind('boss', Boss::class);

$this->app->when(Boss::class)
->needs(Money::class)
->give(Cheque::class);

app()->extend(Money::class, function() {
return new Dollar();
});

$boss = app()->make('boss');
$output = $boss->getA();
$this->assertEquals($output, 1);
}

实例2:子类替换父类,向下拓展

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
class Money
{
public function getAmount(){
return 100;
}
}

class Dollar extends Money
{
public function getAmount()
{
return 1;
}
}

class Cheque extends Money
{
public function getAmount()
{
return 100000;
}
}

public function testClosure()
{
app()->extend(Money::class, function() {
return new Dollar();
});

$money= app()->make(Money::class);
$output = $money->getAmount();
$this->assertEquals($output, 1);
}

实例3:向其他地方拓展,extend的第二个参数是闭包,闭包的第一个参数就是extend第一个参数的实例,在这里就是我们事先绑定的Money::class实例。

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
42
43
44
45
46
47
48
49
50
class Money
{
public function getAmount(){
return 100;
}
}

class Dollar extends Money
{
public function getAmount()
{
return 1;
}
}

class Cheque extends Money
{
public function getAmount()
{
return 100000;
}
}

class Currenty
{
protected $money;

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

public function getAmount()
{
return "harveynorman";
}
}

public function testClosure()
{
app()->bind('money', Money::class);

app()->extend('money', function($money) {
return new Currenty($money);
});

$boss = app()->make('money');
$output = $boss->getAmount();
$this->assertEquals($output, "harveynorman");
}

源码

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
/**
* "Extend" an abstract type in the container.
*
* @param string $abstract
* @param \Closure $closure
* @return void
*
* @throws \InvalidArgumentException
*/
public function extend($abstract, Closure $closure)
{
$abstract = $this->getAlias($abstract);

if (isset($this->instances[$abstract])) {
$this->instances[$abstract] = $closure($this->instances[$abstract], $this);

$this->rebound($abstract);
} else {
$this->extenders[$abstract][] = $closure;

if ($this->resolved($abstract)) {
$this->rebound($abstract);
}
}
}

1、先获取Container中这个id($abstract)的别名。

2、查看容器已有的实例数组instance里面有没有对应的实例,如果有,直接执行我们extend的这个闭包方法,返回值存入这个数组中,就是替换了原来的实例。

2.2.然后使用了rebound(),目的是看看有没有附带的回调函数,触发它,这个我们在回调函数中会提。

这说明extend的时候会触发回调函数。

3.如果instance中没有找到对应的实例,就把这个闭包函数存入extenders数组,做个记录,以后用。

并且如果这个id已经被resolved过了,还要触发rebound函数,触发一些对应的回调函数。

总结

extend的机制就是如果instance中存在实例,就用extend中的闭包执行结果替换掉。如果instances实例列表中不存在就存起来以后备用。

某个适用场景可以是:用子类实例替换父类实例达到扩展的作用。

但不管怎么样,都会触发当前存在的回调函数一次。下一章讲一下回调函数。

0%