1.1 - Laravel 5.6 - Contextual 上下文绑定机制
上下文绑定在分析Container源码的时候是一个比较重要的部分,在了解上下文绑定之前,先解释下什么是上下文:
每一段程序都有很多外部变量。只有像 Add 这种简单的函数才是没有外部变量的。一旦你的一段程序有了外部变量,这段程序就不完整,不能独立运行。你为了使他们运行,就要给所有的外部变量一个一个写一些值进去。这些值的集合就叫上下文。 「编程中什么是「Context (上下文)」?」 - vczh 的回答。
简单说就是解析一个对象的时候,有些对象是需要外部的一些依赖的。那他在创建的时候就要用到上下文
把依赖引入。
而上下文绑定
的意思就是专门处理·实例化时候有依赖关系·情况的一种绑定
上下文绑定在Laravel 5.6
文档中给出了相关示例:
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:
function () {
return Storage::disk('local');
}
而当用户上传视频 VideoController::class,需要使用存储服务(Filesystem::class)时,我们则将云服务驱动,作为实现给到 VideoController::class:
function () {
return Storage::disk('s3');
}
源码:
我们来看下源码的实现。
illuminate\Container\Container.php
1.看下when方法,这个方法直接生成一个ContextualBindingBuilder
对象,传入container对象和$concrete
。$concrete
在这个例子中就是PhotoController::class
和VideoController::class
我们暂且用PhotoController::class为例
public function when($concrete)
{
return new ContextualBindingBuilder($this, $this->getAlias($concrete));
}
2.然后进入这个ContextualBindingBuilder
类看下。
这个类不大,提供了两个方法,needs和give
先看下needs方法,很简单就是把$abstruct
存储起来。
这个例子中的$abstruct
就是Filesystem::class类
然后返回当前对象,以致可以继续链式操作。
public function needs($abstract)
{
$this->needs = $abstract;
return $this;
}
3.再看下give方法,也很简单又重新调用了$container中的addContextualBinding()
,这个就是添加上下文绑定的方法,分别传入的是:
concrete:PhotoController::class的别名(如果有的话)
abstruct:Filesystem::class
implemention:闭包Storage::disk('local');
的返回值。
public function give($implementation)
{
$this->container->addContextualBinding(
$this->concrete, $this->needs, $implementation
);
}
4.然后我们回到Container看看方法addContextualBinding().
也很简单,就是把这些参数存入contextual数组。
(这个特别重要)数组结构形式为:contextual[PhotoController::class][Filesystem::class] = 闭包函数(也可以是一个类路径)
public function addContextualBinding($concrete, $abstract, $implementation)
{
$this->contextual[$concrete][$this->getAlias($abstract)] = $implementation;
}
这就完成了一个上下文绑定。说到底和普通绑定雷同只不过是处理有依赖的对象。
总结
当一个类实例化需要一些外部依赖的时候,就要用到上下文绑定。把外部依赖通过need传递给他。还可以通过give存储when对应的实现(针对抽闲类或者接口甚至是子类)。存入到容器中那个负责上下文的那个数组中,这个数组将会在解析的时候(就是取出某个对象的时候,他对应绑定的依赖也会被取出)做判断。
必须指出,我们通过源码观察,(存储的结构:
contextual[when][need] = implement
)我们发现这个implement可以传入任何类型的值,理论上来说绑定的时候没有任何问题,但是当解析的时候,他必须是一个闭包或者是need的子类或实现类,不然它是无法解析的。在build章节中有分析。
实测用例
提供的类关系
//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类。
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);
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: