详解如何实现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);
      

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/6002.html