详解如何实现Laravel的服务容器的方法示例(6)
9. 上下文绑定
有时侯我们可能有两个类使用同一个接口,但希望在每个类中注入不同的实现,例如两个控制器,分别为它们注入不同的Log服务。
class ApiController
{
public function __construct(Log $log)
{
}
}
class WebController
{
public function __construct(Log $log)
{
}
}
最终我们要用以下方式实现:
// 当ApiController依赖Log时,给它一个RedisLog
$container->addContextualBinding('ApiController','Log',new RedisLog());
// 当WebController依赖Log时,给它一个FileLog
$container->addContextualBinding('WebController','Log',new FileLog());
为了更直观更方便更语义化的使用,我们把这个过程改成链式操作:
$container->when('ApiController')
->needs('Log')
->give(new RedisLog());
我们增加一个$context数组,用来存储上下文。同时增加一个addContextualBinding方法,用来注册上下文绑定。以ApiController为例,$context的真实模样是:
$context['ApiController']['Log'] = new RedisLog();
然后build方法实例化类时,先从上下文中查找依赖参数,就实现了上下文绑定。
接下来,看看链式操作是如何实现的。
首先定义一个类Context,这个类有两个方法,needs和give。
然后在容器中,增加一个when方法,它返回一个Context对象。在Context对象的give方法中,我们已经具备了注册上下文所需要的所有参数,所以就可以在give方法中调用addContextualBinding来注册上下文了。
class ContextContainer extends ExtendContainer
{
// 依赖上下文
protected $context = [];
// 构建一个类,并自动注入服务
public function build($class, array $parameters = [])
{
$reflector = new ReflectionClass($class);
$constructor = $reflector->getConstructor();
if (is_null($constructor)) {
// 没有构造函数,直接new
return new $class();
}
$dependencies = [];
// 获取构造函数所需的参数
foreach ($constructor->getParameters() as $dependency) {
if (isset($this->context[$class]) && isset($this->context[$class][$dependency->getName()])) {
// 先从上下文中查找
$dependencies[] = $this->context[$class][$dependency->getName()];
continue;
}
if (isset($parameters[$dependency->getName()])) {
// 从自定义参数中查找
$dependencies[] = $parameters[$dependency->getName()];
continue;
}
if (is_null($dependency->getClass())) {
// 参数类型不是类或接口时,无法从容器中获取依赖
if ($dependency->isDefaultValueAvailable()) {
// 查找默认值,如果有就使用默认值
$dependencies[] = $dependency->getDefaultValue();
} else {
// 无法提供类所依赖的参数
throw new Exception('找不到依赖参数:' . $dependency->getName());
}
} else {
// 参数类型是一个类时,就用make方法构建该类
$dependencies[] = $this->make($dependency->getClass()->name);
}
}
return $reflector->newInstanceArgs($dependencies);
}
// 绑定上下文
public function addContextualBinding($when, $needs, $give)
{
$this->context[$when][$needs] = $give;
}
// 支持链式方式绑定上下文
public function when($when)
{
return new Context($when, $this);
}
}
class Context
{
protected $when;
protected $needs;
protected $container;
public function __construct($when, ContextContainer $container)
{
$this->when = $when;
$this->container = $container;
}
public function needs($needs)
{
$this->needs = $needs;
return $this;
}
public function give($give)
{
// 调用容器绑定依赖上下文
$this->container->addContextualBinding($this->when, $this->needs, $give);
}
}
// ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- //
class Dog
{
public $name;
public function __construct($name)
{
$this->name = $name;
}
}
class Cat
{
public $name;
public function __construct($name)
{
$this->name = $name;
}
}
$container = new ContextContainer();
// 给Dog类设置上下文绑定
$container->when(Dog::class)
->needs('name')
->give('小狗');
// 给Cat类设置上下文绑定
$container->when(Cat::class)
->needs('name')
->give('小猫');
$dog = $container->make(Dog::class);
$cat = $container->make(Cat::class);
var_dump('Dog:' . $dog->name);
var_dump('Cat:' . $cat->name);
内容版权声明:除非注明,否则皆为本站原创文章。
