SUT父级使用get_class检查依赖项

时间:2018-07-12 18:18:35

标签: php dependency-injection phpunit

它始于我在各处执行空值检查以确保我的交互者具有必要的实体时。幸运的是,我碰到了这篇文章,该文章指出不允许实体处于无效状态do the check in your constructor。现在,我的交互器使用protected static $request来明确说明它们需要哪些实体,然后在实例化期间将其传递。我选择了static,因此可以在创建Interactor实例之前进行检查。

abstract class Interactor {
  protected static $request = [];
  protected $entities = [];

  final public function __construct(Entity ...$entities) {
    $this->setEntities(...$entities);
    $this->checkEntities();
  }

  final private function setEntities(Entity ...$entities) {
    foreach($entities as $entity) {
      $this->setEntity($entity);
    }
  }
  final private function setEntity(Entity $entity){
    $className = get_class($entity);

    if (!in_array($className, static::$request)){
      throw new Exception("Not a requested entity");
    }
    $this->entities[$className] = $entity;
  }
  final private function checkEntities(){
    if (count(static::$request) != count($this->entities))
      throw new Exception("Entity mismatch");

    foreach(static::$request as $index=>$name) {
      if (!array_key_exists($name, $this->entities))
        throw new Exception("Missing requested entity ($name)");
      if (!is_a($this->entities[$name], $name))
        throw new Exception("Not the specified entity");
    }
  }

  final public static function getRequest(){
    return array_values(static::$request);
  }
}

好的,现在我只需要在一个位置进行检查,而不必担心在函数开始时执行空检查。我现在所要解决的问题是我的Interactor正在对照静态类名请求数组检查类名。因此,当我在测试过程中对模拟实体进行DI时,我的父Interactor会抛出一个异常,指出它不在预先批准的列表中。

下面是简化的国际象棋示例,以进行演示:

class Chess extends Interactor {
  protected static $request = ['Piece','Engine','Board'];
}

然后我们有了实体:

abstract class Entity {}
class Piece extends Entity {}
class Engine extends Entity {}
class Board extends Entity {}

最后是我们的测试:

class ChessTest extends TestCase {
  function setUp(){
    $this->piece = $this->getMockBuilder(Piece::class)->getMock();
    $this->engine = $this->getMockBuilder(Engine::class)->getMock();
    $this->board = $this->getMockBuilder(Board::class)->getMock();

    $this->chess = new Chess($this->piece, $this->engine, $this->board);
  }
  function testCanSetup(){
    $this->assertTrue(
      is_a($this->chess, Chess::class)
    );
  }
}

哪个抛出异常:不请求交互器接收实体(Mock_Piece_faaf8b14)

当然,Mock_Piece_faaf8b14不会出现在我们的static::$request数组中,因此注定会引发异常。

到目前为止,我想出的解决方法是将其包含在Entity中:

public function getClassName(){
  return get_called_class();
}

然后在Interactor->setEntity($entity)中而不是使用get_class($entity),我会使用$entity->getClassName()来简化模拟。

我认为我创建Interactor的方式与前面提到的帖子的内容是内联的,只采用了构造函数中的实体。但是,当我注入模拟实体时,一切都感到与众不同。

1)有没有办法避免在我的实体中getClassName()

2)我可以模拟的实体中是否有某些东西在get_class()中被调用?

谢谢您的帮助!

1 个答案:

答案 0 :(得分:1)

您正在检查类的名称是否为$request数组中的键之一。事实并非如此。数组中的键是数字0、1、2,因此会引发异常。我认为您想改用in_array

尽管与此同时,它仍然无法通过模拟传递,因为您正在检查类名是否在$request中。因此,该名称也将根本不存在,并且仍然会引发异常。

如果您的Interactor类正在做的是确保将正确的对象传递给构造函数,为什么不只使用PHP的本机类型提示呢?

您的Chess类变为:

class Chess {
    public function __construct(Piece $piece, Engine $engine, Board $board) { }
}

PHP将确保传入的对象具有正确的类型,并允许您模拟它们以进行测试。

您将获得所需的类型检查,而根本不需要使用getClassName()