Skip to content

Commit

Permalink
#11 Added a handler storage to resolve handlers for parents and imple…
Browse files Browse the repository at this point in the history
…mentations of a message
  • Loading branch information
awd-studio committed May 31, 2020
1 parent 469f7a7 commit 7889aa6
Show file tree
Hide file tree
Showing 5 changed files with 413 additions and 0 deletions.
111 changes: 111 additions & 0 deletions src/Bus/Handler/ParentsAwareHandlerRegistry.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php

declare(strict_types=1);

namespace AwdStudio\Bus\Handler;

use AwdStudio\Bus\Registry\ImplementationParser;

/**
* @implements HandlerRegistry<callable(object $message, mixed ...$extraParams): mixed>
*/
final class ParentsAwareHandlerRegistry implements HandlerRegistry
{
/**
* @var \AwdStudio\Bus\Handler\HandlerRegistry
*
* @psalm-var HandlerRegistry<callable(object $message, mixed ...$extraParams): mixed>
* @phpstan-var HandlerRegistry<callable(object $message, mixed ...$extraParams): mixed>
*/
private $handlers;

/** @var \AwdStudio\Bus\Registry\ImplementationParser */
private $reflector;

/**
* @var array
*
* @psalm-var array<class-string, class-string[]>
* @phpstan-var array<class-string, class-string[]>
*/
private $parsedMap;

/**
* @param \AwdStudio\Bus\Handler\HandlerRegistry $handlers
* @param \AwdStudio\Bus\Registry\ImplementationParser $reflector
*
* @psalm-param HandlerRegistry<callable(object $message, mixed ...$extraParams): mixed> $handlers
* @phpstan-param HandlerRegistry<callable(object $message, mixed ...$extraParams): mixed> $handlers
*/
public function __construct(HandlerRegistry $handlers, ImplementationParser $reflector)
{
$this->handlers = $handlers;
$this->reflector = $reflector;
$this->parsedMap = [];
}

/**
* {@inheritdoc}
*/
public function register(string $messageId, string $handlerId): void
{
$this->handlers->register($messageId, $handlerId);
}

/**
* {@inheritdoc}
*/
public function add(string $messageId, callable $handler): void
{
$this->handlers->add($messageId, $handler);
}

/**
* {@inheritdoc}
*/
public function has(string $messageId): bool
{
$has = $this->handlers->has($messageId);
if (false === $has) {
foreach ($this->parse($messageId) as $implementation) {
if ($this->handlers->has($implementation)) {
return true;
}
}
}

return $has;
}

/**
* {@inheritdoc}
*/
public function get(string $messageId): \Traversable
{
foreach (\array_merge([$messageId], $this->parse($messageId)) as $implementation) {
yield from $this->handlers->get($implementation);
}
}

/**
* Parses and caches the result.
*
* @param string $messageId
*
* @return array
*
* @psalm-param class-string $messageId
* @phpstan-param class-string $messageId
*
* @psalm-return class-string[]
* @phpstan-return class-string[]
*/
private function parse(string $messageId): array
{
if (false === isset($this->parsedMap[$messageId])) {
$this->parsedMap[$messageId] = $this->reflector->parse($messageId);
}

return $this->parsedMap[$messageId];
}
}
23 changes: 23 additions & 0 deletions src/Bus/Registry/ImplementationParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace AwdStudio\Bus\Registry;

interface ImplementationParser
{
/**
* Returns a list of all implementation of a class.
*
* @param string $messageId
*
* @return string[]
*
* @psalm-param class-string $messageId
* @phpstan-param class-string $messageId
*
* @psalm-return class-string[]
* @phpstan-return class-string[]
*/
public function parse(string $messageId): array;
}
30 changes: 30 additions & 0 deletions src/Bus/Registry/ReflectionImplementationParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace AwdStudio\Bus\Registry;

final class ReflectionImplementationParser implements ImplementationParser
{
/**
* {@inheritdoc}
*/
public function parse(string $messageId): array
{
$result = [];
$messageReflection = new \ReflectionClass($messageId);

$reflectionClasses = $messageReflection->getInterfaces();
foreach ($reflectionClasses as $interface) {
$result[] = $interface->getName();
}

$parent = $messageReflection->getParentClass();
while (false !== $parent) {
$result[] = $parent->getName();
$parent = $parent->getParentClass();
}

return $result;
}
}
189 changes: 189 additions & 0 deletions tests/Unit/Bus/Handler/ParentsAwareHandlersTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
<?php

declare(strict_types=1);

namespace AwdStudio\Tests\Unit\Bus\Handler;

use AwdStudio\Bus\Handler\ParentsAwareHandlerRegistry;
use AwdStudio\Bus\Handler\HandlerRegistry;
use AwdStudio\Bus\HandlerLocator;
use AwdStudio\Bus\Registry\ImplementationParser;
use AwdStudio\Tests\BusTestCase;
use Prophecy\Argument;

/**
* @coversDefaultClass \AwdStudio\Bus\Handler\ParentsAwareHandlerRegistry
*/
final class ParentsAwareHandlersTest extends BusTestCase
{
/** @var \AwdStudio\Bus\Handler\ParentsAwareHandlerRegistry */
private $instance;

/** @var \AwdStudio\Bus\Handler\HandlerRegistry|\Prophecy\Prophecy\ObjectProphecy */
private $handlersRegistryProphesy;

/** @var \AwdStudio\Bus\Registry\ImplementationParser|\Prophecy\Prophecy\ObjectProphecy */
private $parserProphesy;

protected function setUp(): void
{
parent::setUp();

$this->handlersRegistryProphesy = $this->prophesize(HandlerRegistry::class);
$this->parserProphesy = $this->prophesize(ImplementationParser::class);

$this->instance = new ParentsAwareHandlerRegistry(
$this->handlersRegistryProphesy->reveal(),
$this->parserProphesy->reveal()
);
}

/**
* @covers ::__construct
*/
public function testMustImplementAHandlers(): void
{
$this->assertInstanceOf(HandlerLocator::class, $this->instance);
}

/**
* @covers ::register
*/
public function testMustRegisterViaHandlers(): void
{
$this->handlersRegistryProphesy
->register(Argument::exact('Foo'), Argument::exact('FooHandler'))
->shouldBeCalledOnce();

$this->instance->register('Foo', 'FooHandler');
}

/**
* @covers ::add
*/
public function testMustAddViaHandlers(): void
{
$handler = static function (): void { };

$this->handlersRegistryProphesy
->add(Argument::exact('Foo'), Argument::exact($handler))
->shouldBeCalledOnce();

$this->instance->add('Foo', $handler);
}

/**
* @covers ::has
*/
public function testMustReturnTrueIfHandlersHasAHandler(): void
{
$this->handlersRegistryProphesy
->has(Argument::exact('Foo'))
->willReturn(true)
->shouldBeCalledOnce();

$this->assertTrue($this->instance->has('Foo'));
}

/**
* @covers ::parse
*/
public function testMustParseParentsIfHandlersDoesNotHaveAHandler(): void
{
$this->handlersRegistryProphesy
->has(Argument::exact('Foo'))
->willReturn(false)
->shouldBeCalledOnce();

$this->parserProphesy
->parse(Argument::exact('Foo'))
->willReturn([])
->shouldBeCalledOnce();

$this->instance->has('Foo');
}

/**
* @covers ::has
*/
public function testMustReturnTrueIfHandlersContainsAHandlerFromOneOfParsedResults(): void
{
$this->handlersRegistryProphesy
->has(Argument::any())
->willReturn(false, true);

$this->parserProphesy
->parse(Argument::exact('Foo'))
->willReturn(['IFoo']);

$this->assertTrue($this->instance->has('Foo'));
}

/**
* @covers ::has
*/
public function testMustReturnFalseIfThereIsNoOneHandlerEvenForParents(): void
{
$this->handlersRegistryProphesy
->has(Argument::any())
->willReturn(false, false, false);

$this->parserProphesy
->parse(Argument::exact('Foo'))
->willReturn(['IFoo', 'IBar']);

$this->assertFalse($this->instance->has('Foo'));
}

/**
* @covers ::get
*/
public function testMustAskHandlersFromHandlers(): void
{
$this->handlersRegistryProphesy
->get(Argument::exact('Foo'))
->willYield([])
->shouldBeCalledOnce();

$this->parserProphesy
->parse(Argument::exact('Foo'))
->willReturn([])
->shouldBeCalledOnce();

\iterator_to_array($this->instance->get('Foo'));
}

/**
* @covers ::parse
*/
public function testMustParseImplementationsToYieldMoreHandlers(): void
{
$this->handlersRegistryProphesy
->get(Argument::any())
->willYield([])
->shouldBeCalledTimes(3);

$this->parserProphesy
->parse(Argument::exact('Foo'))
->willReturn(['IFoo', 'IBar'])
->shouldBeCalledOnce();

\iterator_to_array($this->instance->get('Foo'));
}

/**
* @covers ::get
*/
public function testMustReturnNothingIfThereAreNoHandlers(): void
{
$this->handlersRegistryProphesy
->get(Argument::any())
->willYield([]);

$this->parserProphesy
->parse(Argument::exact('Foo'))
->willReturn(['IFoo', 'IBar']);

$this->assertEmpty(\iterator_to_array($this->instance->get('Foo')));
}
}
Loading

0 comments on commit 7889aa6

Please sign in to comment.