Container contextual上下文绑定机制

思考并回答以下问题:

  • 一旦你的一段程序有了外部变量,这段程序就不完整,不能独立运行。你为了使他们运行,就要给所有的外部变量一个一个写一些值进去。这些值的集合就叫上下文。怎么理解?
  • 上下文绑定把数据存入了这样的数组contextual[PhotoController::class][Filesystem::class] = 闭包函数(也可以是一个类路径)。怎么理解?只有这个数组contextual,在make的时候会用到。
  • 闭包函数可以实现懒加载,怎么理解?
  • Container类的属性基本都是数组。为什么?
  • $resolved数组存储的[‘abstractClassName’=>true/false]。$bindings存储的是二维数组[‘abstractClassName’=>[]]。methodBindings存储的是[‘methodName’=>$closure]。$instances存储[‘abstractClassName’=>$instance对象实例]。
  • $alias数组存储的是[‘别名’=>’abstractClassName’]。$buildStack一维数组[concrete]是要构建的具体类。$with数组是参数的数组。
  • 有三种角色,需要实例化的类A,A的依赖抽象类B,B的具体实现C。怎么理解?

简介

上下文绑定在分析Container源码的时候是一个比较重要的部分,在了解上下文绑定之前,先解释下什么是上下文:

每一段程序都有很多外部变量。只有像Add这种简单的函数才是没有外部变量的。一旦你的一段程序有了外部变量,这段程序就不完整,不能独立运行。你为了使他们运行,就要给所有的外部变量一个一个写一些值进去。这些值的集合就叫上下文。

简单说就是解析一个对象的时候,有些对象是需要外部的一些依赖的。那他在创建的时候就要用到“上下文”把依赖引入。

上下文绑定的意思就是专门处理实例化时候,有依赖关系情况的一种绑定。

上下文绑定在Laravel文档中给出了相关示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use Illuminate\Support\Facades\Storage;
use App\Http\Controllers\PhotoController;
use App\Http\Controllers\VideoController;
use Illuminate\Contracts\Filesystem\Filesystem;

$this->app->when(PhotoController::class)
->needs(Filesystem::class)
->give(function () {
return Storage::disk('local');
});

$this->app->when(VideoController::class)
->needs(Filesystem::class)
->give(function () {
return Storage::disk('s3');
});

这是项目中常会用到的存储功能,得益于Laravel内置集成了FlySystem的Filesystem接口,我们很容易实现多种存储服务的项目。

示例中将用户头像存储到本地,将用户上传的小视频存储到云服务。那么这个时候就需要区分这样不同的使用场景(即上下文或者说环境)。

当用户存储头像(PhotoController::class)需要使用存储服务(Filesystem::class)时,我们将本地存储驱动,作为实现给到PhotoController::class。

1
2
3
function () {
return Storage::disk('local');
}

而当用户上传视频VideoController::class,需要使用存储服务(Filesystem::class)时,我们则将云服务驱动,作为实现给到VideoController::class。

1
2
3
function () {
return Storage::disk('s3');
}

实测用例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if (! function_exists('app')) 
{
/**
* Get the available container instance.
*
* @param string|null $abstract
* @param array $parameters
* @return mixed|\Illuminate\Contracts\Foundation\Application
*/
function app($abstract = null, array $parameters = [])
{
if (is_null($abstract))
{
return Container::getInstance();
}

return Container::getInstance()->make($abstract, $parameters);
}
}

Illuminate/Container/Container.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Container implements ArrayAccess, ContainerContract
{
/**
* The current globally available container (if any).
*
* @var static
*/
protected static $instance;

/**
* Get the globally available instance of the container.
*
* @return static
*/
public static function getInstance()
{
if (is_null(static::$instance))
{
static::$instance = new static;
}

return static::$instance;
}
}

提供的类关系:

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
// interface接口money
interface Money
{
public function getAmount();
}

// 实现类Cheque(支票)
class Cheque implements Money
{
public function getAmount()
{
return 100000;
}
}

// Boss类
class Boss
{
private $money;

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

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

测试1:绑定Boss类,Boss类依赖Money类的一个对象。最后绑定的实现是Cheque类。

1
2
3
4
5
6
7
8
9
10
11
12
public function testClosure()
{
$this->app->when(Boss::class)
->needs(Money::class)
->give(Cheque::class);

$boss = app()->make(Boss::class);

$output = $boss->getA();

$this->assertEquals($output, 100000);
}

源码

Illuminate\Container\Util.php

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
<?php

namespace Illuminate\Container;

use Closure;

class Util
{
/**
* If the given value is not an array and not null, wrap it in one.
*
* From Arr::wrap() in Illuminate\Support.
*
* @param mixed $value
* @return array
*/
public static function arrayWrap($value)
{
if (is_null($value))
{
return [];
}

return is_array($value) ? $value : [$value];
}

/**
* Return the default value of the given value.
*
* From global value() helper in Illuminate\Support.
*
* @param mixed $value
* @return mixed
*/
public static function unwrapIfClosure($value)
{
return $value instanceof Closure ? $value() : $value;
}
}

1、看下when方法。

illuminate\Container\Container.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* Define a contextual binding.
*
* @param array|string $concrete
* @return \Illuminate\Contracts\Container\ContextualBindingBuilder
*/
public function when($concrete)
{
$aliases = [];

foreach (Util::arrayWrap($concrete) as $c)
{
$aliases[] = $this->getAlias($c);
}

return new ContextualBindingBuilder($this, $aliases);
}

这个方法直接生成一个ContextualBindingBuilder对象,传入container对象和$concrete。

$concrete在这个例子中就是PhotoController::class和VideoController::class。

我们暂且用PhotoController::class为例。

2、然后进入这个ContextualBindingBuilder类看下。

Illuminate\Contracts\Container\ContextualBindingBuilder.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php

namespace Illuminate\Contracts\Container;

interface ContextualBindingBuilder
{
/**
* Define the abstract target that depends on the context.
*
* @param string $abstract
* @return $this
*/
public function needs($abstract);

/**
* Define the implementation for the contextual binding.
*
* @param \Closure|string $implementation
* @return void
*/
public function give($implementation);
}

Illuminate\Container\ContextualBindingBuilder.php

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
65
66
67
68
69
70
<?php

namespace Illuminate\Container;

use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Container\ContextualBindingBuilder as ContextualBindingBuilderContract;

class ContextualBindingBuilder implements ContextualBindingBuilderContract
{
/**
* The underlying container instance.
*
* @var \Illuminate\Contracts\Container\Container
*/
protected $container;

/**
* The concrete instance.
*
* @var string|array
*/
protected $concrete;

/**
* The abstract target.
*
* @var string
*/
protected $needs;

/**
* Create a new contextual binding builder.
*
* @param \Illuminate\Contracts\Container\Container $container
* @param string|array $concrete
* @return void
*/
public function __construct(Container $container, $concrete)
{
$this->concrete = $concrete;
$this->container = $container;
}

/**
* Define the abstract target that depends on the context.
*
* @param string $abstract
* @return $this
*/
public function needs($abstract)
{
$this->needs = $abstract;

return $this;
}

/**
* Define the implementation for the contextual binding.
*
* @param \Closure|string $implementation
* @return void
*/
public function give($implementation)
{
foreach (Util::arrayWrap($this->concrete) as $concrete)
{
$this->container->addContextualBinding($concrete, $this->needs, $implementation);
}
}
}

这个类不大,提供了两个方法,needs和give。

先看下needs方法,很简单就是把$abstract存储起来。

这个例子中的$abstract就是Filesystem::class类。

然后返回当前对象,以致可以继续链式操作。

1
2
3
4
5
6
7
8
9
10
11
12
/**
* Define the abstract target that depends on the context.
*
* @param string $abstract
* @return $this
*/
public function needs($abstract)
{
$this->needs = $abstract;

return $this;
}

3、再看下give方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Define the implementation for the contextual binding.
*
* @param \Closure|string $implementation
* @return void
*/
public function give($implementation)
{
foreach (Util::arrayWrap($this->concrete) as $concrete)
{
$this->container->addContextualBinding($concrete, $this->needs, $implementation);
}
}

很简单又重新调用了Container中的addContextualBinding(),这个就是添加上下文绑定的方法,分别传入的是:

  • concrete:PhotoController::class的别名(如果有的话)。
  • abstract:Filesystem::class。
  • implementation:闭包Storage::disk(“local”);的返回值。

4、然后我们回到Container看看方法addContextualBinding()。

1
2
3
4
5
6
7
8
9
10
11
12
/**
* Add a contextual binding to the container.
*
* @param string $concrete
* @param string $abstract
* @param \Closure|string $implementation
* @return void
*/
public function addContextualBinding($concrete, $abstract, $implementation)
{
$this->contextual[$concrete][$this->getAlias($abstract)] = $implementation;
}

也很简单,就是把这些参数存入contextual数组。

(这个特别重要)数组结构形式为:

contextual[PhotoController::class][Filesystem::class] = 闭包函数(也可以是一个类路径)

这就完成了一个上下文绑定。说到底和普通绑定雷同,只不过是处理有依赖的对象。

总结

当一个类实例化需要一些外部依赖的时候,就要用到上下文绑定。把外部依赖通过needs传递给他。还可以通过give存储when对应的实现(针对抽象类或者接口甚至是子类)。存入到容器中那个负责上下文的那个数组中,这个数组将会在解析的时候(就是取出某个对象的时候,他对应绑定的依赖也会被取出)做判断。

必须指出,我们通过源码观察,(存储的结构:contextual[when][needs] = implement)我们发现这个implement可以传入任何类型的值,理论上来说绑定的时候没有任何问题,但是当解析的时候,他必须是一个闭包或者是needs的子类或实现类,不然它是无法解析的。在build章节中有分析。

0%