Skip to content

Commit

Permalink
allow only one handler for command
Browse files Browse the repository at this point in the history
  • Loading branch information
sokil committed Feb 3, 2017
1 parent 71623a9 commit cf33009
Show file tree
Hide file tree
Showing 10 changed files with 279 additions and 201 deletions.
40 changes: 14 additions & 26 deletions src/Bus.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,35 +43,23 @@ public function handle($command)
throw new \InvalidArgumentException('Passed command not configured in bus');
}

$handlerDefinitions = $this->handlerDefinitions[$commandClassName];
$handlerDefinition = $this->handlerDefinitions[$commandClassName];

// order handlers by priority
usort($handlerDefinitions, function($handlerDefinition1, $handlerDefinition2) {
if ($handlerDefinition1['priority'] === $handlerDefinition2['priority']) {
return 0;
}
// get handler
$handlerServiceId = $handlerDefinition['handler'];
$handler = $this->commandHandlerServiceResolver->get($handlerServiceId);

return ($handlerDefinition1['priority'] < $handlerDefinition2['priority']) ? -1 : 1;
});

// handle
foreach ($handlerDefinitions as $handlerDefinition) {
// get handler
$handlerServiceId = $handlerDefinition['handler'];
$handler = $this->commandHandlerServiceResolver->get($handlerServiceId);

// check if handler may handle command
if (!$handler->supports($command)) {
throw new CommandUnacceptableByHandlerException(sprintf(
'Command %s is not supported by handler %s',
get_class($handler),
get_class($command)
));
}

// execute command by handler
$handler->handle($command);
// check if handler may handle command
if (!$handler->supports($command)) {
throw new CommandUnacceptableByHandlerException(sprintf(
'Command %s is not supported by handler %s',
get_class($command),
get_class($handler)
));
}

// execute command by handler
$handler->handle($command);
}


Expand Down
14 changes: 11 additions & 3 deletions src/DependencyInjection/RegisterCommandHandlerCompilerPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;

class RegisterCommandHandlerCompilerPass implements CompilerPassInterface
{
Expand All @@ -20,9 +21,16 @@ public function process(ContainerBuilder $container)
foreach ($container->findTaggedServiceIds(self::TAG_NAME) as $commandBusHandlerServiceId => $commandBusHandlerTags) {
foreach ($commandBusHandlerTags as $commandBusHandlerTagParams) {
$commandClassName = $commandBusHandlerTagParams['command_class'];
$priority = !empty($commandBusHandlerTagParams['priority']) ? (int)$commandBusHandlerTagParams['priority'] : 0;
$handlers[$commandClassName][] = [
'priority' => $priority,

if (isset($handlers[$commandClassName])) {
throw new InvalidArgumentException(sprintf(
'Handler %s already configured for command %s',
$handlers[$commandClassName]['handler'],
$commandClassName
));
}

$handlers[$commandClassName] = [
'handler' => $commandBusHandlerServiceId
];
}
Expand Down
128 changes: 128 additions & 0 deletions tests/AbstractTestCase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<?php

namespace Sokil\CommandBusBundle;

use Sokil\CommandBusBundle\DependencyInjection\RegisterCommandHandlerCompilerPass;
use Sokil\CommandBusBundle\Stub\CloseAccountCommand;
use Sokil\CommandBusBundle\Stub\CloseAccountCommandHandler;
use Sokil\CommandBusBundle\Stub\OpenAccountCommand;
use Sokil\CommandBusBundle\Stub\ProcessTransactionCommandHandler;
use Sokil\CommandBusBundle\Stub\SendMoneyCommand;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\DependencyInjection\Reference;

abstract class AbstractTestCase extends \PHPUnit_Framework_TestCase
{
private function createAccountRepositoryDefinition()
{
$accountRepositoryServiceDefinition = new Definition(\SplFixedArray::class);
$accountRepositoryServiceDefinition
->setFactory([\SplFixedArray::class, 'fromArray'])
->setArguments([
[
0 => 10,
1 => 10,
2 => 10,
3 => 10,
]
]);

return $accountRepositoryServiceDefinition;
}

private function createCloseAccountCommandHandlerDefinition()
{
$closeAccountCommandHandlerServiceDefinition = new Definition();
$closeAccountCommandHandlerServiceDefinition
->setClass(CloseAccountCommandHandler::class)
->setArguments([new Reference('account_repository')])
->addTag(
RegisterCommandHandlerCompilerPass::TAG_NAME,
[
'command_class' => CloseAccountCommand::class,
'priority' => 100,
]
);

return $closeAccountCommandHandlerServiceDefinition;
}

private function createProcessTransactionCommandHandlerDefinition()
{
$processTransactionCommandHandlerServiceDefinition = new Definition();
$processTransactionCommandHandlerServiceDefinition
->setClass(ProcessTransactionCommandHandler::class)
->setArguments([new Reference('account_repository')])
->addTag(
RegisterCommandHandlerCompilerPass::TAG_NAME,
[
'command_class' => SendMoneyCommand::class,
'priority' => 200,
]
);

return $processTransactionCommandHandlerServiceDefinition;
}

private function createCommandHandlerWithNotSupportedCommandDefinition()
{
$processTransactionCommandHandlerServiceDefinition = new Definition();
$processTransactionCommandHandlerServiceDefinition
->setClass(CloseAccountCommandHandler::class)
->setArguments([new Reference('account_repository')])
->addTag(
RegisterCommandHandlerCompilerPass::TAG_NAME,
[
'command_class' => OpenAccountCommand::class,
'priority' => 200,
]
);

return $processTransactionCommandHandlerServiceDefinition;
}

/**
* @return ContainerBuilder
*/
protected function createContainer()
{
$containerBuilder = new ContainerBuilder();

// load services
$loader = new YamlFileLoader($containerBuilder, new FileLocator(__DIR__.'/../src/Resources/config'));
$loader->load('services.yml');

// account repository
$containerBuilder->setDefinition('account_repository', $this->createAccountRepositoryDefinition());

// close account handler definition
$containerBuilder->setDefinition(
'close_account_command_handler',
$this->createCloseAccountCommandHandlerDefinition()
);

// process transaction handler definition
$containerBuilder->setDefinition(
'process_transaction_command_handler',
$this->createProcessTransactionCommandHandlerDefinition()
);

// handler definition with not supported command
$containerBuilder->setDefinition(
'command_handler_with_unsupported_command',
$this->createCommandHandlerWithNotSupportedCommandDefinition()
);

// process compiler pass
$compilerPass = new RegisterCommandHandlerCompilerPass();
$compilerPass->process($containerBuilder);

// build container
$containerBuilder->compile();

return $containerBuilder;
}
}
118 changes: 14 additions & 104 deletions tests/BusTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,106 +2,16 @@

namespace Sokil\CommandBusBundle;

use Sokil\CommandBusBundle\Bus\Exception\CommandUnacceptableByHandlerException;
use Sokil\CommandBusBundle\DependencyInjection\RegisterCommandHandlerCompilerPass;
use Sokil\CommandBusBundle\Stub\CheckFraudTransactionCommandHandler;
use Sokil\CommandBusBundle\Stub\ProcessTransactionCommandHandler;
use Sokil\CommandBusBundle\Stub\OpenAccountCommand;
use Sokil\CommandBusBundle\Stub\SendMoneyCommand;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\DependencyInjection\Reference;

class BusTest extends \PHPUnit_Framework_TestCase
class BusTest extends AbstractTestCase
{
/**
* @return ContainerBuilder
*/
private function createContainer()
{
$containerBuilder = new ContainerBuilder();

// load services
$loader = new YamlFileLoader($containerBuilder, new FileLocator(__DIR__.'/../src/Resources/config'));
$loader->load('services.yml');

// fraud repository
$fraudRepositoryServiceDefinition = new Definition(\SplFixedArray::class);
$fraudRepositoryServiceDefinition
->setFactory([\SplFixedArray::class, 'fromArray'])
->setArguments([
[
0 => false,
1 => false,
2 => true,
3 => true,
]
]);

$containerBuilder->setDefinition('fraud_repository', $fraudRepositoryServiceDefinition);

// account repository
$accountRepositoryServiceDefinition = new Definition(\SplFixedArray::class);
$accountRepositoryServiceDefinition
->setFactory([\SplFixedArray::class, 'fromArray'])
->setArguments([
[
0 => 10,
1 => 10,
2 => 10,
3 => 10,
]
]);

$containerBuilder->setDefinition('account_repository', $accountRepositoryServiceDefinition);

// first handler definition
$checkFraudTransactionCommandHandlerServiceDefinition = new Definition();
$checkFraudTransactionCommandHandlerServiceDefinition
->setClass(CheckFraudTransactionCommandHandler::class)
->setArguments([new Reference('fraud_repository')])
->addTag(
RegisterCommandHandlerCompilerPass::TAG_NAME,
[
'command_class' => SendMoneyCommand::class,
'priority' => 100,
]
);

$containerBuilder->setDefinition(
'check_fraud_transaction_command_handler',
$checkFraudTransactionCommandHandlerServiceDefinition
);

// second handler definition
$processTransactionCommandHandlerServiceDefinition = new Definition();
$processTransactionCommandHandlerServiceDefinition
->setClass(ProcessTransactionCommandHandler::class)
->setArguments([new Reference('account_repository')])
->addTag(
RegisterCommandHandlerCompilerPass::TAG_NAME,
[
'command_class' => SendMoneyCommand::class,
'priority' => 200,
]
);

$containerBuilder->setDefinition(
'process_transaction_command_handler',
$processTransactionCommandHandlerServiceDefinition
);

// process compiler pass
$compilerPass = new RegisterCommandHandlerCompilerPass();
$compilerPass->process($containerBuilder);

// build container
$containerBuilder->compile();

return $containerBuilder;
}

public function testHandle_SeparateHandlers_Success()
public function testHandle_SuccessfullyHandled()
{
$container = $this->createContainer();

Expand All @@ -111,15 +21,6 @@ public function testHandle_SeparateHandlers_Success()
->get(RegisterCommandHandlerCompilerPass::COMMAND_BUS_SERVICE_ID)
->handle($sendMoneyCommand);

// assert process log
$this->assertSame(
[
'CheckFraudTransaction',
'ProcessTransaction',
],
$sendMoneyCommand->getLog()
);

// assert amount
$this->assertSame(
[
Expand All @@ -132,8 +33,17 @@ public function testHandle_SeparateHandlers_Success()
);
}

public function testHandle_HandlerChain_Success()
/**
* @expectedException \Sokil\CommandBusBundle\Bus\Exception\CommandUnacceptableByHandlerException
*/
public function testHandle_WrongCommandPassedToHandler()
{
$container = $this->createContainer();

$command = new OpenAccountCommand(42, 42);

$container
->get(RegisterCommandHandlerCompilerPass::COMMAND_BUS_SERVICE_ID)
->handle($command);
}
}

0 comments on commit cf33009

Please sign in to comment.