Skip to content

Commit

Permalink
feat: Add command and query bus.
Browse files Browse the repository at this point in the history
  • Loading branch information
b00gizm committed Jan 12, 2023
1 parent 54ae159 commit 848da2c
Show file tree
Hide file tree
Showing 20 changed files with 618 additions and 36 deletions.
9 changes: 9 additions & 0 deletions src/Contracts/Application/Command.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace GeekCell\Ddd\Contracts\Application;

interface Command
{
}
16 changes: 16 additions & 0 deletions src/Contracts/Application/CommandBus.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace GeekCell\Ddd\Contracts\Application;

interface CommandBus
{
/**
* Dispatch a command to the appropriate handler.
*
* @param Command $command
* @return mixed
*/
public function dispatch(Command $command): mixed;
}
18 changes: 18 additions & 0 deletions src/Contracts/Application/CommandHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace GeekCell\Ddd\Contracts\Application;

use GeekCell\Ddd\Contracts\Core\Interactable;

interface CommandHandler extends Interactable
{
/**
* Execute a command.
*
* @param Command $command
* @return mixed
*/
public function execute(Command $command): mixed;
}
9 changes: 9 additions & 0 deletions src/Contracts/Application/Query.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace GeekCell\Ddd\Contracts\Application;

interface Query
{
}
16 changes: 16 additions & 0 deletions src/Contracts/Application/QueryBus.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace GeekCell\Ddd\Contracts\Application;

interface QueryBus
{
/**
* Dispatch a query to the appropriate handler.
*
* @param Query $query
* @return mixed
*/
public function dispatch(Query $query): mixed;
}
18 changes: 18 additions & 0 deletions src/Contracts/Application/QueryHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace GeekCell\Ddd\Contracts\Application;

use GeekCell\Ddd\Contracts\Core\Interactable;

interface QueryHandler extends Interactable
{
/**
* Execute a query.
*
* @param Query $query
* @return mixed
*/
public function execute(Query $query): mixed;
}
9 changes: 9 additions & 0 deletions src/Contracts/Core/Interactable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace GeekCell\Ddd\Contracts\Core;

interface Interactable
{
}
98 changes: 98 additions & 0 deletions src/Infrastructure/InMemory/AbstractBus.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?php

declare(strict_types=1);

namespace GeekCell\Ddd\Infrastructure\InMemory;

use GeekCell\Ddd\Contracts\Core\Interactable;
use GeekCell\Ddd\Support\Attributes\ForType;

abstract class AbstractBus
{
/**
* @var array<string, mixed>
*/
protected array $handlers = [];

/**
* @return array<string, mixed>
*/
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()
];
}
}
}
}
}
49 changes: 49 additions & 0 deletions src/Infrastructure/InMemory/CommandBus.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

namespace GeekCell\Ddd\Infrastructure\InMemory;

use GeekCell\Ddd\Contracts\Application\Command;
use GeekCell\Ddd\Contracts\Application\CommandBus as CommandBusInterface;
use GeekCell\Ddd\Contracts\Application\CommandHandler;
use GeekCell\Ddd\Support\Attributes\For\Command as ForCommand;

class CommandBus extends AbstractBus implements CommandBusInterface
{
/**
* @inheritDoc
*/
public function dispatch(Command $command): mixed
{
$commandType = get_class($command);
if (!array_key_exists($commandType, $this->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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
49 changes: 49 additions & 0 deletions src/Infrastructure/InMemory/QueryBus.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

namespace GeekCell\Ddd\Infrastructure\InMemory;

use GeekCell\Ddd\Contracts\Application\Query;
use GeekCell\Ddd\Contracts\Application\QueryBus as QueryBusInterface;
use GeekCell\Ddd\Contracts\Application\QueryHandler;
use GeekCell\Ddd\Support\Attributes\For\Query as ForQuery;

final class QueryBus extends AbstractBus implements QueryBusInterface
{
/**
* @inheritDoc
*/
public function dispatch(Query $query): mixed
{
$queryType = get_class($query);
if (!array_key_exists($queryType, $this->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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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[]
Expand Down
18 changes: 18 additions & 0 deletions src/Support/Attributes/For/Command.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace GeekCell\Ddd\Support\Attributes\For;

use Attribute;
use GeekCell\Ddd\Contracts\Application\Command as CommandInterface;
use GeekCell\Ddd\Support\Attributes\ForType;

#[Attribute(Attribute::TARGET_CLASS)]
class Command extends ForType
{
protected function supports(): string
{
return CommandInterface::class;
}
}
18 changes: 18 additions & 0 deletions src/Support/Attributes/For/Query.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace GeekCell\Ddd\Support\Attributes\For;

use Attribute;
use GeekCell\Ddd\Contracts\Application\Query as QueryInterface;
use GeekCell\Ddd\Support\Attributes\ForType;

#[Attribute(Attribute::TARGET_CLASS)]
class Query extends ForType
{
protected function supports(): string
{
return QueryInterface::class;
}
}
Loading

0 comments on commit 848da2c

Please sign in to comment.