详解如何实现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);
内容版权声明:除非注明,否则皆为本站原创文章。