diff --git a/src/Contracts/Application/Command.php b/src/Contracts/Application/Command.php new file mode 100644 index 0000000..1f4b222 --- /dev/null +++ b/src/Contracts/Application/Command.php @@ -0,0 +1,9 @@ + + */ + protected array $handlers = []; + + /** + * @return array + */ + public function getHandlers(): array + { + return $this->handlers; + } + + /** + * Register a handler. + * + * The handler can be a callable or an object that implements + * the Interactable interface and has the ForType class attribute. + * + * @param mixed $handler + * @return void + */ + abstract public function registerHandler(mixed $handler): void; + + /** + * Register a callable handler. + * + * @param callable $callable The callable to register + * @param string $parameterType The type of the parameter that the callable + * + * @return void + */ + protected function registerCallableHandler( + callable $callable, + string $parameterType, + ): void + { + $reflectionMethod = new \ReflectionMethod($callable, '__invoke'); + foreach ($reflectionMethod->getParameters() as $parameter) { + $type = $parameter->getType(); + if ( + is_subclass_of($type->getName(), $parameterType) && + $type->allowsNull() === false) { + $this->handlers[$type->getName()] = $callable; + } + } + } + + /** + * Register a handler class. + * + * @param Interactable $handler The handler to register + * @param string $attributeType Class attribute which is a + * subclass of ForType + * @return void + */ + protected function registerHandlerClass( + Interactable $handler, + string $attributeType, + ): void + { + $reflectionClass = new \ReflectionClass($handler); + $attributes = $reflectionClass->getAttributes(); + foreach ($attributes as $attribute) { + if ( + is_subclass_of($attributeType, ForType::class) && + $attribute->getName() !== $attributeType) { + continue; + } + + /** @var ForType $context */ + $context = $attribute->newInstance(); + if (!$context->isValid()) { + return; + } + + foreach ($reflectionClass->getMethods() as $method) { + if ($method->getName() === $context->getHandler()) { + $this->handlers[$context->getType()] = [ + $handler, + $method->getName() + ]; + } + } + } + } +} diff --git a/src/Infrastructure/InMemory/CommandBus.php b/src/Infrastructure/InMemory/CommandBus.php new file mode 100644 index 0000000..3fece0c --- /dev/null +++ b/src/Infrastructure/InMemory/CommandBus.php @@ -0,0 +1,49 @@ +handlers)) { + return null; + } + + $handler = $this->handlers[$commandType]; + if (is_callable($handler)) { + return $handler($command); + } + + if (is_array($handler)) { + [$handler, $method] = $handler; + return call_user_func([$handler, $method], $command); + } + } + + /** + * @inheritDoc + */ + public function registerHandler(mixed $handler): void + { + if (is_callable($handler)) { + $this->registerCallableHandler($handler, Command::class); + return; + } + + if ($handler instanceof CommandHandler) { + $this->registerHandlerClass($handler, ForCommand::class); + } + } +} diff --git a/src/Infrastructure/InMemoryPaginator.php b/src/Infrastructure/InMemory/Paginator.php similarity index 90% rename from src/Infrastructure/InMemoryPaginator.php rename to src/Infrastructure/InMemory/Paginator.php index bfd4b27..31b0f84 100644 --- a/src/Infrastructure/InMemoryPaginator.php +++ b/src/Infrastructure/InMemory/Paginator.php @@ -2,15 +2,15 @@ declare(strict_types=1); -namespace GeekCell\Ddd\Infrastructure; +namespace GeekCell\Ddd\Infrastructure\InMemory; use EmptyIterator; -use GeekCell\Ddd\Contracts\Domain\Paginator; +use GeekCell\Ddd\Contracts\Domain\Paginator as PaginatorInterface; use GeekCell\Ddd\Domain\Collection; use LimitIterator; use Traversable; -class InMemoryPaginator implements Paginator +class Paginator implements PaginatorInterface { public function __construct( private readonly Collection $collection, diff --git a/src/Infrastructure/InMemory/QueryBus.php b/src/Infrastructure/InMemory/QueryBus.php new file mode 100644 index 0000000..ab4857b --- /dev/null +++ b/src/Infrastructure/InMemory/QueryBus.php @@ -0,0 +1,49 @@ +handlers)) { + return null; + } + + $handler = $this->handlers[$queryType]; + if (is_callable($handler)) { + return $handler($query); + } + + if (is_array($handler)) { + [$handler, $method] = $handler; + return call_user_func([$handler, $method], $query); + } + } + + /** + * @inheritDoc + */ + public function registerHandler(mixed $handler): void + { + if (is_callable($handler)) { + $this->registerCallableHandler($handler, Query::class); + return; + } + + if ($handler instanceof QueryHandler) { + $this->registerHandlerClass($handler, ForQuery::class); + } + } +} diff --git a/src/Infrastructure/InMemoryRepository.php b/src/Infrastructure/InMemory/Repository.php similarity index 89% rename from src/Infrastructure/InMemoryRepository.php rename to src/Infrastructure/InMemory/Repository.php index b4759ea..7e9c866 100644 --- a/src/Infrastructure/InMemoryRepository.php +++ b/src/Infrastructure/InMemory/Repository.php @@ -2,14 +2,14 @@ declare(strict_types=1); -namespace GeekCell\Ddd\Infrastructure; +namespace GeekCell\Ddd\Infrastructure\InMemory; use Assert\Assert; -use GeekCell\Ddd\Contracts\Domain\Repository as Repository; -use GeekCell\Ddd\Infrastructure\InMemoryPaginator; +use GeekCell\Ddd\Contracts\Domain\Repository as RepositoryInterface; +use GeekCell\Ddd\Infrastructure\InMemory\Paginator as InMemoryPaginator; use Traversable; -abstract class InMemoryRepository implements Repository +abstract class Repository implements RepositoryInterface { /** * @var T[] diff --git a/src/Support/Attributes/For/Command.php b/src/Support/Attributes/For/Command.php new file mode 100644 index 0000000..2d7b1b4 --- /dev/null +++ b/src/Support/Attributes/For/Command.php @@ -0,0 +1,18 @@ +type; + } + + public function getHandler(): string + { + return $this->handler; + } + + public function isValid(): bool + { + return is_subclass_of($this->getType(), $this->supports()); + } + + abstract protected function supports(): string; +} diff --git a/tests/Fixtures/Counter.php b/tests/Fixtures/Counter.php new file mode 100644 index 0000000..4da66b3 --- /dev/null +++ b/tests/Fixtures/Counter.php @@ -0,0 +1,12 @@ +registerHandler(new TestCommandHandler()); + + // When + $result = $commandBus->dispatch(new TestCommand()); + + // Then + self::assertEquals(TestCommandHandler::class, $result); + } + + public function testAddHandlerClassWithoutAttributes(): void + { + // Given + $commandBus = new InMemoryCommandBus(); + $commandBus->registerHandler(new TestCommandHandlerWithoutAttributes()); + + // When + $result = $commandBus->dispatch(new TestCommand()); + + // Then + self::assertNull($result); + } + + public function testAddCallableHandler(): void + { + // Given + $commandBus = new InMemoryCommandBus(); + $commandBus->registerHandler(new CallableCommandHandler()); + + // When + $result = $commandBus->dispatch(new TestCommand()); + + // Then + self::assertEquals(CallableCommandHandler::class, $result); + } + + public function testAddCallableHandlerWithUnknownCommand(): void + { + // Given + $commandBus = new InMemoryCommandBus(); + $commandBus->registerHandler(new CallableCommandHandler()); + + // When + $result = $commandBus->dispatch(new UnknownCommand()); + + // Then + self::assertNull($result); + } +} diff --git a/tests/Infrastructure/InMemoryPaginatorTest.php b/tests/Infrastructure/InMemory/PaginatorTest.php similarity index 95% rename from tests/Infrastructure/InMemoryPaginatorTest.php rename to tests/Infrastructure/InMemory/PaginatorTest.php index 1fc78f7..e3eaf96 100644 --- a/tests/Infrastructure/InMemoryPaginatorTest.php +++ b/tests/Infrastructure/InMemory/PaginatorTest.php @@ -2,23 +2,18 @@ declare(strict_types=1); +namespace GeekCell\Ddd\Tests\Infrastructure\InMemory; + +use ArrayIterator; +use EmptyIterator; use GeekCell\Ddd\Domain\Collection; -use GeekCell\Ddd\Infrastructure\InMemoryPaginator; +use GeekCell\Ddd\Infrastructure\InMemory\Paginator as InMemoryPaginator; +use GeekCell\Ddd\Tests\Fixtures\Counter; +use LimitIterator; +use Mockery; use PHPUnit\Framework\TestCase; -/** - * Test fixture for InMemoryPaginator. - * - * @package GeekCell\Ddd\Tests\Infrastructure - */ -class Counter -{ - public function __construct(private int $value) - { - } -} - -class InMemoryPaginatorTest extends TestCase +class PaginatorTest extends TestCase { public function testGetCurrentPage(): void { diff --git a/tests/Infrastructure/InMemory/QueryBusTest.php b/tests/Infrastructure/InMemory/QueryBusTest.php new file mode 100644 index 0000000..425a977 --- /dev/null +++ b/tests/Infrastructure/InMemory/QueryBusTest.php @@ -0,0 +1,114 @@ +registerHandler(new TestQueryHandler()); + + // When + $result = $queryBus->dispatch(new TestQuery()); + + // Then + self::assertEquals(TestQueryHandler::class, $result); + } + + public function testAddHandlerClassWithoutAttributes(): void + { + // Given + $queryBus = new InMemoryQueryBus(); + $queryBus->registerHandler(new TestQueryHandlerWithoutAttributes()); + + // When + $result = $queryBus->dispatch(new TestQuery()); + + // Then + self::assertNull($result); + } + + public function testAddCallableHandler(): void + { + // Given + $queryBus = new InMemoryQueryBus(); + $queryBus->registerHandler(new CallableQueryHandler()); + + // When + $result = $queryBus->dispatch(new TestQuery()); + + // Then + self::assertEquals(CallableQueryHandler::class, $result); + } + + public function testAddCallableHandlerWithUnknownCommand(): void + { + // Given + $queryBus = new InMemoryQueryBus(); + $queryBus->registerHandler(new CallableQueryHandler()); + + // When + $result = $queryBus->dispatch(new UnknownQuery()); + + // Then + self::assertNull($result); + } +} diff --git a/tests/Infrastructure/InMemoryRepositoryTest.php b/tests/Infrastructure/InMemory/RepositoryTest.php similarity index 92% rename from tests/Infrastructure/InMemoryRepositoryTest.php rename to tests/Infrastructure/InMemory/RepositoryTest.php index d25b204..3d2483f 100644 --- a/tests/Infrastructure/InMemoryRepositoryTest.php +++ b/tests/Infrastructure/InMemory/RepositoryTest.php @@ -2,26 +2,15 @@ declare(strict_types=1); -namespace GeekCell\Ddd\Tests\Infrastructure; +namespace GeekCell\Ddd\Tests\Infrastructure\InMemory; use Assert; use GeekCell\Ddd\Domain\Collection; -use GeekCell\Ddd\Infrastructure\InMemoryPaginator; -use GeekCell\Ddd\Infrastructure\InMemoryRepository; +use GeekCell\Ddd\Infrastructure\InMemory\Paginator as InMemoryPaginator; +use GeekCell\Ddd\Infrastructure\InMemory\Repository as InMemoryRepository; +use GeekCell\Ddd\Tests\Fixtures\Counter; use PHPUnit\Framework\TestCase; -/** - * Test fixture for InMemoryRepository. - * - * @package GeekCell\Ddd\Tests\Infrastructure - */ -class Counter -{ - public function __construct(private int $value) - { - } -} - /** * Test fixture for InMemoryRepository. *