Skip to content
This repository was archived by the owner on Sep 6, 2025. It is now read-only.

Commit 7223844

Browse files
committed
Added tactician command bus implementation
1 parent afba94a commit 7223844

File tree

8 files changed

+264
-39
lines changed

8 files changed

+264
-39
lines changed

src/CommandBus/CommandBusInterface.php

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,4 @@ interface CommandBusInterface
1313
* @param mixed $command A command that will be dispatched
1414
*/
1515
public function execute($command);
16-
17-
/**
18-
* Subscribes the command handler to this CommandBus
19-
* @param string $commandName The command name to map to
20-
* @param mixed $handler
21-
*/
22-
public function subscribe($commandName, $handler);
2316
}

src/CommandBus/EventDispatchingCommandBus.php

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,4 @@ public function execute($command)
4141
throw $e;
4242
}
4343
}
44-
45-
/**
46-
* {@inheritDoc}
47-
*/
48-
public function subscribe($commandName, $handler)
49-
{
50-
$this->commandBus->subscribe($commandName, $handler);
51-
}
5244
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
namespace HelloFresh\Engine\CommandBus\Exception;
4+
5+
/**
6+
* Thrown when a specific handler object can not be used on a command object.
7+
*
8+
* The most common reason is the receiving method is missing or incorrectly
9+
* named.
10+
*/
11+
class CanNotInvokeHandlerException extends \BadMethodCallException
12+
{
13+
/**
14+
* @var mixed
15+
*/
16+
private $command;
17+
18+
/**
19+
* @param mixed $command
20+
* @param string $reason
21+
*
22+
* @return static
23+
*/
24+
public static function forCommand($command, $reason)
25+
{
26+
$type = is_object($command) ? get_class($command) : gettype($command);
27+
28+
$exception = new static(
29+
'Could not invoke handler for command ' . $type .
30+
' for reason: ' . $reason
31+
);
32+
$exception->command = $command;
33+
34+
return $exception;
35+
}
36+
37+
/**
38+
* Returns the command that could not be invoked
39+
*
40+
* @return mixed
41+
*/
42+
public function getCommand()
43+
{
44+
return $this->command;
45+
}
46+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
/**
3+
* Created by PhpStorm.
4+
* User: ilelis
5+
* Date: 20/06/16
6+
* Time: 14:53
7+
*/
8+
9+
namespace HelloFresh\Engine\CommandBus\Exception;
10+
11+
/**
12+
* No handler could be found for the given command.
13+
*/
14+
class MissingHandlerException extends \OutOfBoundsException
15+
{
16+
/**
17+
* @var string
18+
*/
19+
private $commandName;
20+
21+
/**
22+
* @param string $commandName
23+
*
24+
* @return static
25+
*/
26+
public static function forCommand($commandName)
27+
{
28+
$exception = new static('Missing handler for command ' . $commandName);
29+
$exception->commandName = $commandName;
30+
31+
return $exception;
32+
}
33+
34+
/**
35+
* @return string
36+
*/
37+
public function getCommandName()
38+
{
39+
return $this->commandName;
40+
}
41+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace HelloFresh\Engine\CommandBus\Handler;
4+
5+
use HelloFresh\Engine\CommandBus\Exception\MissingHandlerException;
6+
7+
/**
8+
* Service locator for handler objects
9+
*/
10+
interface HandlerLocatorInterface
11+
{
12+
/**
13+
* Retrieves the handler for a specified command
14+
*
15+
* @param string $commandName
16+
*
17+
* @return object
18+
*
19+
* @throws MissingHandlerException
20+
*/
21+
public function getHandlerForCommand($commandName);
22+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
namespace HelloFresh\Engine\CommandBus\Handler;
4+
5+
use HelloFresh\Engine\CommandBus\Exception\MissingHandlerException;
6+
7+
class InMemoryLocator implements HandlerLocatorInterface
8+
{
9+
/**
10+
* @var object[]
11+
*/
12+
protected $handlers = [];
13+
14+
/**
15+
* @param array $commandClassToHandlerMap
16+
*/
17+
public function __construct(array $commandClassToHandlerMap = [])
18+
{
19+
$this->addHandlers($commandClassToHandlerMap);
20+
}
21+
22+
/**
23+
* Bind a handler instance to receive all commands with a certain class
24+
*
25+
* @param object $handler Handler to receive class
26+
* @param string $commandClassName Command class e.g. "My\TaskAddedCommand"
27+
*/
28+
public function addHandler($handler, $commandClassName)
29+
{
30+
$this->handlers[$commandClassName] = $handler;
31+
}
32+
33+
/**
34+
* Allows you to add multiple handlers at once.
35+
* @param array $commandClassToHandlerMap
36+
*/
37+
protected function addHandlers(array $commandClassToHandlerMap)
38+
{
39+
foreach ($commandClassToHandlerMap as $commandClass => $handler) {
40+
$this->addHandler($handler, $commandClass);
41+
}
42+
}
43+
44+
/**
45+
* Returns the handler bound to the command's class name.
46+
*
47+
* @param string $commandName
48+
*
49+
* @return object
50+
*/
51+
public function getHandlerForCommand($commandName)
52+
{
53+
if (!isset($this->handlers[$commandName])) {
54+
throw MissingHandlerException::forCommand($commandName);
55+
}
56+
57+
return $this->handlers[$commandName];
58+
}
59+
}

src/CommandBus/SimpleCommandBus.php

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,12 @@
22

33
namespace HelloFresh\Engine\CommandBus;
44

5-
use Assert\Assertion;
6-
use Collections\Dictionary;
7-
use Collections\MapInterface;
85
use Collections\Queue;
6+
use HelloFresh\Engine\CommandBus\Exception\CanNotInvokeHandlerException;
7+
use HelloFresh\Engine\CommandBus\Handler\HandlerLocatorInterface;
98

109
class SimpleCommandBus implements CommandBusInterface
1110
{
12-
/**
13-
* @var MapInterface
14-
*/
15-
private $commandHandlers;
16-
1711
/**
1812
* @var Queue
1913
*/
@@ -25,21 +19,18 @@ class SimpleCommandBus implements CommandBusInterface
2519
private $isDispatching = false;
2620

2721
/**
28-
* SimpleCommandBus constructor.
22+
* @var HandlerLocatorInterface
2923
*/
30-
public function __construct()
31-
{
32-
$this->commandHandlers = new Dictionary();
33-
$this->queue = new Queue();
34-
}
24+
private $handlerLocator;
3525

3626
/**
37-
* {@inheritDoc}
27+
* SimpleCommandBus constructor.
28+
* @param HandlerLocatorInterface $handlerLocator
3829
*/
39-
public function subscribe($commandName, $handler)
30+
public function __construct(HandlerLocatorInterface $handlerLocator)
4031
{
41-
\Assert\that($commandName)->notEmpty()->string();
42-
$this->commandHandlers->add($commandName, $handler);
32+
$this->queue = new Queue();
33+
$this->handlerLocator = $handlerLocator;
4334
}
4435

4536
/**
@@ -66,11 +57,16 @@ public function execute($command)
6657
*/
6758
private function processCommand($command)
6859
{
69-
$this->commandHandlers->filterWithKey(function ($commandName, $handler) use ($command) {
70-
return $commandName === get_class($command);
71-
})->each(function ($handler) use ($command) {
72-
Assertion::methodExists('handle', $handler);
73-
$handler->handle($command);
74-
});
60+
$handler = $this->handlerLocator->getHandlerForCommand(get_class($command));
61+
$methodName = 'handler';
62+
63+
if (!is_callable([$handler, $methodName])) {
64+
throw CanNotInvokeHandlerException::forCommand(
65+
$command,
66+
"Method '{$methodName}' does not exist on handler"
67+
);
68+
}
69+
70+
$handler->handle($command);
7571
}
7672
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php
2+
3+
namespace HelloFresh\Engine\CommandBus;
4+
5+
use Assert\Assertion;
6+
use Collections\Dictionary;
7+
use Collections\MapInterface;
8+
use Collections\Queue;
9+
10+
class TacticianCommandBus implements CommandBusInterface
11+
{
12+
/**
13+
* @var MapInterface
14+
*/
15+
private $commandHandlers;
16+
17+
/**
18+
* @var Queue
19+
*/
20+
private $queue;
21+
22+
/**
23+
* @var bool
24+
*/
25+
private $isDispatching = false;
26+
27+
/**
28+
* SimpleCommandBus constructor.
29+
*/
30+
public function __construct()
31+
{
32+
$this->commandHandlers = new Dictionary();
33+
$this->queue = new Queue();
34+
}
35+
36+
/**
37+
* {@inheritDoc}
38+
*/
39+
public function subscribe($commandName, $handler)
40+
{
41+
\Assert\that($commandName)->notEmpty()->string();
42+
$this->commandHandlers->add($commandName, $handler);
43+
}
44+
45+
/**
46+
* {@inheritDoc}
47+
*/
48+
public function execute($command)
49+
{
50+
$this->queue->enqueue($command);
51+
52+
if (!$this->isDispatching) {
53+
$this->isDispatching = true;
54+
try {
55+
while (!$this->queue->isEmpty()) {
56+
$this->processCommand($this->queue->pop());
57+
}
58+
} finally {
59+
$this->isDispatching = false;
60+
}
61+
}
62+
}
63+
64+
/**
65+
* @param $command
66+
*/
67+
private function processCommand($command)
68+
{
69+
$this->commandHandlers->filterWithKey(function ($commandName, $handler) use ($command) {
70+
return $commandName === get_class($command);
71+
})->each(function ($handler) use ($command) {
72+
Assertion::methodExists('handle', $handler);
73+
$handler->handle($command);
74+
});
75+
}
76+
}

0 commit comments

Comments
 (0)