It started when I was performing null checks everywhere to make sure I have the necessary entities for my interactor. Fortunately, I came across this post which points towards not allowing the entities to be in an invalid state do the check in your constructor. Now my Interactors use protected static $request
to state explicitly which entities they require, which are then passed in during instantiation. I chose static so the check could be done prior to creating an instance of the 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);
}
}
Ok great, now I just do the check in a single location and I don't need to worry about performing null checks at the beginning of my functions. The problem with the way I am going about it now is that my Interactor is checking the class name against a static class name request array. Thus, when I DI the mocked entities during testing, my parent Interactor throws an exception saying it isn't in the pre approved list.
To demonstrate is the following simplified Chess example:
class Chess extends Interactor {
protected static $request = ['Piece','Engine','Board'];
}
Then we have our Entities:
abstract class Entity {}
class Piece extends Entity {}
class Engine extends Entity {}
class Board extends Entity {}
And finally our test:
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)
);
}
}
Which throws Exception: Interactor receiving entity not requested (Mock_Piece_faaf8b14)
Of course Mock_Piece_faaf8b14 is not going to be in our static::$request
array, so this is destined to throw an exception.
The workaround I have come up with so far is to include in Entity:
public function getClassName(){
return get_called_class();
}
Then in Interactor->setEntity($entity)
instead of using get_class($entity)
I would use $entity->getClassName()
which then becomes trivial to mock.
I thought the way I had created the Interactor was inline with what the previously mentioned post was getting at, only take the entities in the constructor. However, it all feel apart when I injected mocked entities.
1) Is there a way to avoid getClassName()
in my entities?
2) Is there something in the entities I can mock that gets called in get_class()
instead?
Thank you for your help!