feature #27275 [Messenger] Allow to scope handlers per bus (ogizanagi, sroze)

This PR was merged into the 4.1 branch.

Discussion
----------

[Messenger] Allow to scope handlers per bus

| Q             | A
| ------------- | ---
| Branch?       | 4.1 <!-- see below -->
| Bug fix?      | no
| New feature?  | yes <!-- don't forget to update src/**/CHANGELOG.md files -->
| BC breaks?    | no     <!-- see https://symfony.com/bc -->
| Deprecations? | no <!-- don't forget to update UPGRADE-*.md and src/**/CHANGELOG.md files -->
| Tests pass?   | yes    <!-- please add some, will be required by reviewers -->
| Fixed tickets | N/A   <!-- #-prefixed issue number(s), if any -->
| License       | MIT
| Doc PR        | todo

<img width="970" alt="screenshot 2018-05-15 a 18 34 11" src="https://user-images.githubusercontent.com/2211145/40070551-b36d1f50-586e-11e8-8a44-c6a1503312dd.PNG">

This prevents from dispatching a message in the wrong bus. It works especially great with PSR-4 discovery in DI:

```yaml
command_handlers:
    namespace: App\Message\
    resource: '%kernel.project_dir%/src/Message/*CommandHandler.php'
    tags:
        - { name: messenger.message_handler, bus: command_bus }

query_handlers:
    namespace: App\Message\
    resource: '%kernel.project_dir%/src/Message/*QueryHandler.php'
    tags:
        - { name: messenger.message_handler, bus: query_bus }
```

<details>

<summary>(or in some layered architecture)</summary>

```yaml
command_handlers:
    namespace: App\Application\
    resource: '%kernel.project_dir%/src/Application/**/Handler/*CommandHandler.php'
    tags:
        - { name: messenger.message_handler, bus: command_bus }

query_handlers:
    namespace: App\Application\
    resource: '%kernel.project_dir%/src/Application/**/Handler/*QueryHandler.php'
    tags:
        - { name: messenger.message_handler, bus: query_bus }
```

</details>

---
It also updates (and add tests for) the `DebugCommand`, so we can inspect easily where is supposed to be handled each message.
It's totally optional, and if the bus isn't provided, then it stays available in every bus.

Commits
-------

fd810cdee0 Uses `protected` for test functions
9d658e915e [Messenger] Allow to scope handlers per bus
This commit is contained in:
Samuel ROZE 2018-05-20 10:49:44 +01:00
commit 7497ad4a68
12 changed files with 477 additions and 70 deletions

View File

@ -1483,7 +1483,7 @@ class FrameworkExtension extends Extension
} }
$container->setParameter($busId.'.middleware', $middleware); $container->setParameter($busId.'.middleware', $middleware);
$container->setDefinition($busId, (new Definition(MessageBus::class, array(array())))->addTag('messenger.bus')); $container->register($busId, MessageBus::class)->addArgument(array())->addTag('messenger.bus');
if ($busId === $config['default_bus']) { if ($busId === $config['default_bus']) {
$container->setAlias('message_bus', $busId); $container->setAlias('message_bus', $busId);

View File

@ -7,11 +7,6 @@
<services> <services>
<defaults public="false" /> <defaults public="false" />
<!-- Handlers -->
<service id="messenger.handler_resolver" class="Symfony\Component\Messenger\Handler\Locator\ContainerHandlerLocator">
<argument type="service" id="service_container"/>
</service>
<!-- Asynchronous --> <!-- Asynchronous -->
<service id="messenger.asynchronous.routing.sender_locator" class="Symfony\Component\Messenger\Asynchronous\Routing\SenderLocator"> <service id="messenger.asynchronous.routing.sender_locator" class="Symfony\Component\Messenger\Asynchronous\Routing\SenderLocator">
<argument type="service" id="messenger.sender_locator" /> <argument type="service" id="messenger.sender_locator" />
@ -32,7 +27,7 @@
<!-- Middleware --> <!-- Middleware -->
<service id="messenger.middleware.allow_no_handler" class="Symfony\Component\Messenger\Middleware\AllowNoHandlerMiddleware" abstract="true" /> <service id="messenger.middleware.allow_no_handler" class="Symfony\Component\Messenger\Middleware\AllowNoHandlerMiddleware" abstract="true" />
<service id="messenger.middleware.call_message_handler" class="Symfony\Component\Messenger\Middleware\HandleMessageMiddleware" abstract="true"> <service id="messenger.middleware.call_message_handler" class="Symfony\Component\Messenger\Middleware\HandleMessageMiddleware" abstract="true">
<argument type="service" id="messenger.handler_resolver" /> <argument /> <!-- Bus handler resolver -->
</service> </service>
<service id="messenger.middleware.validation" class="Symfony\Component\Messenger\Middleware\ValidationMiddleware" abstract="true"> <service id="messenger.middleware.validation" class="Symfony\Component\Messenger\Middleware\ValidationMiddleware" abstract="true">

View File

@ -12,6 +12,8 @@
namespace Symfony\Component\Messenger\Command; namespace Symfony\Component\Messenger\Command;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Console\Style\SymfonyStyle;
@ -31,9 +33,9 @@ class DebugCommand extends Command
public function __construct(array $mapping) public function __construct(array $mapping)
{ {
parent::__construct();
$this->mapping = $mapping; $this->mapping = $mapping;
parent::__construct();
} }
/** /**
@ -42,13 +44,18 @@ class DebugCommand extends Command
protected function configure() protected function configure()
{ {
$this $this
->setDescription('Lists messages you can dispatch using the message bus') ->addArgument('bus', InputArgument::OPTIONAL, sprintf('The bus id (one of %s)', implode(', ', array_keys($this->mapping))), null)
->setDescription('Lists messages you can dispatch using the message buses')
->setHelp(<<<'EOF' ->setHelp(<<<'EOF'
The <info>%command.name%</info> command displays all messages that can be The <info>%command.name%</info> command displays all messages that can be
dispatched using the message bus: dispatched using the message buses:
<info>php %command.full_name%</info> <info>php %command.full_name%</info>
Or for a specific bus only:
<info>php %command.full_name% command_bus</info>
EOF EOF
) )
; ;
@ -61,21 +68,33 @@ EOF
{ {
$io = new SymfonyStyle($input, $output); $io = new SymfonyStyle($input, $output);
$io->title('Messenger'); $io->title('Messenger');
$io->text('The following messages can be dispatched:');
$io->newLine();
$tableRows = array(); $mapping = $this->mapping;
foreach ($this->mapping as $message => $handlers) { if ($bus = $input->getArgument('bus')) {
$tableRows[] = array(sprintf('<fg=cyan>%s</fg=cyan>', $message)); if (!isset($mapping[$bus])) {
foreach ($handlers as $handler) { throw new RuntimeException(sprintf('Bus "%s" does not exist. Known buses are %s.', $bus, implode(', ', array_keys($this->mapping))));
$tableRows[] = array(sprintf(' handled by %s', $handler));
} }
$mapping = array($bus => $mapping[$bus]);
} }
if ($tableRows) { foreach ($mapping as $bus => $handlersByMessage) {
$io->table(array(), $tableRows); $io->section($bus);
} else {
$io->text('No messages were found that have valid handlers.'); $tableRows = array();
foreach ($handlersByMessage as $message => $handlers) {
$tableRows[] = array(sprintf('<fg=cyan>%s</fg=cyan>', $message));
foreach ($handlers as $handler) {
$tableRows[] = array(sprintf(' handled by <info>%s</>', $handler));
}
}
if ($tableRows) {
$io->text('The following messages can be dispatched:');
$io->newLine();
$io->table(array(), $tableRows);
} else {
$io->warning(sprintf('No handled message found in bus "%s".', $bus));
}
} }
} }
} }

View File

@ -20,6 +20,7 @@ use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Messenger\Handler\ChainHandler; use Symfony\Component\Messenger\Handler\ChainHandler;
use Symfony\Component\Messenger\Handler\Locator\ContainerHandlerLocator;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface; use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
use Symfony\Component\Messenger\Handler\MessageSubscriberInterface; use Symfony\Component\Messenger\Handler\MessageSubscriberInterface;
use Symfony\Component\Messenger\TraceableMessageBus; use Symfony\Component\Messenger\TraceableMessageBus;
@ -51,11 +52,13 @@ class MessengerPass implements CompilerPassInterface
*/ */
public function process(ContainerBuilder $container) public function process(ContainerBuilder $container)
{ {
if (!$container->hasDefinition('messenger.handler_resolver')) { if (!$container->has('message_bus')) {
return; return;
} }
$busIds = array();
foreach ($container->findTaggedServiceIds($this->busTag) as $busId => $tags) { foreach ($container->findTaggedServiceIds($this->busTag) as $busId => $tags) {
$busIds[] = $busId;
if ($container->hasParameter($busMiddlewareParameter = $busId.'.middleware')) { if ($container->hasParameter($busMiddlewareParameter = $busId.'.middleware')) {
$this->registerBusMiddleware($container, $busId, $container->getParameter($busMiddlewareParameter)); $this->registerBusMiddleware($container, $busId, $container->getParameter($busMiddlewareParameter));
@ -69,16 +72,20 @@ class MessengerPass implements CompilerPassInterface
$this->registerReceivers($container); $this->registerReceivers($container);
$this->registerSenders($container); $this->registerSenders($container);
$this->registerHandlers($container); $this->registerHandlers($container, $busIds);
} }
private function registerHandlers(ContainerBuilder $container) private function registerHandlers(ContainerBuilder $container, array $busIds)
{ {
$definitions = array(); $definitions = array();
$handlersByMessage = array(); $handlersByBusAndMessage = array();
foreach ($container->findTaggedServiceIds($this->handlerTag, true) as $serviceId => $tags) { foreach ($container->findTaggedServiceIds($this->handlerTag, true) as $serviceId => $tags) {
foreach ($tags as $tag) { foreach ($tags as $tag) {
if (isset($tag['bus']) && !\in_array($tag['bus'], $busIds, true)) {
throw new RuntimeException(sprintf('Invalid handler service "%s": bus "%s" specified on the tag "%s" does not exist (known ones are: %s).', $serviceId, $tag['bus'], $this->handlerTag, implode(', ', $busIds)));
}
$r = $container->getReflectionClass($container->getDefinition($serviceId)->getClass()); $r = $container->getReflectionClass($container->getDefinition($serviceId)->getClass());
if (isset($tag['handles'])) { if (isset($tag['handles'])) {
@ -88,6 +95,7 @@ class MessengerPass implements CompilerPassInterface
} }
$priority = $tag['priority'] ?? 0; $priority = $tag['priority'] ?? 0;
$handlerBuses = (array) ($tag['bus'] ?? $busIds);
foreach ($handles as $messageClass => $method) { foreach ($handles as $messageClass => $method) {
if (\is_int($messageClass)) { if (\is_int($messageClass)) {
@ -123,38 +131,57 @@ class MessengerPass implements CompilerPassInterface
$definitions[$serviceId = '.messenger.method_on_object_wrapper.'.ContainerBuilder::hash($messageClass.':'.$messagePriority.':'.$serviceId.':'.$method)] = $wrapperDefinition; $definitions[$serviceId = '.messenger.method_on_object_wrapper.'.ContainerBuilder::hash($messageClass.':'.$messagePriority.':'.$serviceId.':'.$method)] = $wrapperDefinition;
} }
$handlersByMessage[$messageClass][$messagePriority][] = new Reference($serviceId); foreach ($handlerBuses as $handlerBus) {
$handlersByBusAndMessage[$handlerBus][$messageClass][$messagePriority][] = $serviceId;
}
} }
} }
} }
foreach ($handlersByMessage as $message => $handlers) { foreach ($handlersByBusAndMessage as $bus => $handlersByMessage) {
krsort($handlersByMessage[$message]); foreach ($handlersByMessage as $message => $handlersByPriority) {
$handlersByMessage[$message] = array_merge(...$handlersByMessage[$message]); krsort($handlersByPriority);
$handlersByBusAndMessage[$bus][$message] = array_unique(array_merge(...$handlersByPriority));
}
} }
$handlersLocatorMapping = array(); $handlersLocatorMappingByBus = array();
foreach ($handlersByMessage as $message => $handlers) { foreach ($handlersByBusAndMessage as $bus => $handlersByMessage) {
if (1 === \count($handlers)) { foreach ($handlersByMessage as $message => $handlersIds) {
$handlersLocatorMapping['handler.'.$message] = current($handlers); if (1 === \count($handlersIds)) {
} else { $handlersLocatorMappingByBus[$bus]['handler.'.$message] = new Reference(current($handlersIds));
$d = new Definition(ChainHandler::class, array($handlers)); } else {
$d->setPrivate(true); $chainHandler = new Definition(ChainHandler::class, array(array_map(function (string $handlerId): Reference {
$serviceId = hash('sha1', $message); return new Reference($handlerId);
$definitions[$serviceId] = $d; }, $handlersIds)));
$handlersLocatorMapping['handler.'.$message] = new Reference($serviceId); $chainHandler->setPrivate(true);
$serviceId = '.messenger.chain_handler.'.ContainerBuilder::hash($bus.$message);
$definitions[$serviceId] = $chainHandler;
$handlersLocatorMappingByBus[$bus]['handler.'.$message] = new Reference($serviceId);
}
} }
} }
$container->addDefinitions($definitions); $container->addDefinitions($definitions);
$handlerResolver = $container->getDefinition('messenger.handler_resolver'); foreach ($busIds as $bus) {
$handlerResolver->replaceArgument(0, ServiceLocatorTagPass::register($container, $handlersLocatorMapping)); $container->register($resolverName = "$bus.messenger.handler_resolver", ContainerHandlerLocator::class)
->setArgument(0, ServiceLocatorTagPass::register($container, $handlersLocatorMappingByBus[$bus] ?? array()))
;
if ($container->has($callMessageHandlerId = "$bus.middleware.call_message_handler")) {
$container->getDefinition($callMessageHandlerId)
->replaceArgument(0, new Reference($resolverName))
;
}
}
if ($container->hasDefinition('console.command.messenger_debug')) { if ($container->hasDefinition('console.command.messenger_debug')) {
$container->getDefinition('console.command.messenger_debug') $debugCommandMapping = $handlersByBusAndMessage;
->replaceArgument(0, array_map(function (array $handlers): array { foreach ($busIds as $bus) {
return array_map('strval', $handlers); if (!isset($debugCommandMapping[$bus])) {
}, $handlersByMessage)); $debugCommandMapping[$bus] = array();
}
}
$container->getDefinition('console.command.messenger_debug')->replaceArgument(0, $debugCommandMapping);
} }
} }

View File

@ -0,0 +1,155 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Messenger\Tests\Command;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Tester\CommandTester;
use Symfony\Component\Messenger\Command\DebugCommand;
use Symfony\Component\Messenger\Tests\Fixtures\DummyCommand;
use Symfony\Component\Messenger\Tests\Fixtures\DummyCommandHandler;
use Symfony\Component\Messenger\Tests\Fixtures\DummyQuery;
use Symfony\Component\Messenger\Tests\Fixtures\DummyQueryHandler;
use Symfony\Component\Messenger\Tests\Fixtures\MultipleBusesMessage;
use Symfony\Component\Messenger\Tests\Fixtures\MultipleBusesMessageHandler;
/**
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
*/
class DebugCommandTest extends TestCase
{
protected function setUp()
{
putenv('COLUMNS='.(119 + strlen(PHP_EOL)));
}
protected function tearDown()
{
putenv('COLUMNS=');
}
public function testOutput()
{
$command = new DebugCommand(
array(
'command_bus' => array(
DummyCommand::class => array(DummyCommandHandler::class),
MultipleBusesMessage::class => array(MultipleBusesMessageHandler::class),
),
'query_bus' => array(
DummyQuery::class => array(DummyQueryHandler::class),
MultipleBusesMessage::class => array(MultipleBusesMessageHandler::class),
),
)
);
$tester = new CommandTester($command);
$tester->execute(array(), array('decorated' => false));
$this->assertSame(<<<TXT
Messenger
=========
command_bus
-----------
The following messages can be dispatched:
---------------------------------------------------------------------------------------
Symfony\Component\Messenger\Tests\Fixtures\DummyCommand
handled by Symfony\Component\Messenger\Tests\Fixtures\DummyCommandHandler
Symfony\Component\Messenger\Tests\Fixtures\MultipleBusesMessage
handled by Symfony\Component\Messenger\Tests\Fixtures\MultipleBusesMessageHandler
---------------------------------------------------------------------------------------
query_bus
---------
The following messages can be dispatched:
---------------------------------------------------------------------------------------
Symfony\Component\Messenger\Tests\Fixtures\DummyQuery
handled by Symfony\Component\Messenger\Tests\Fixtures\DummyQueryHandler
Symfony\Component\Messenger\Tests\Fixtures\MultipleBusesMessage
handled by Symfony\Component\Messenger\Tests\Fixtures\MultipleBusesMessageHandler
---------------------------------------------------------------------------------------
TXT
, $tester->getDisplay(true)
);
$tester->execute(array('bus' => 'query_bus'), array('decorated' => false));
$this->assertSame(<<<TXT
Messenger
=========
query_bus
---------
The following messages can be dispatched:
---------------------------------------------------------------------------------------
Symfony\Component\Messenger\Tests\Fixtures\DummyQuery
handled by Symfony\Component\Messenger\Tests\Fixtures\DummyQueryHandler
Symfony\Component\Messenger\Tests\Fixtures\MultipleBusesMessage
handled by Symfony\Component\Messenger\Tests\Fixtures\MultipleBusesMessageHandler
---------------------------------------------------------------------------------------
TXT
, $tester->getDisplay(true)
);
}
public function testOutputWithoutMessages()
{
$command = new DebugCommand(array('command_bus' => array(), 'query_bus' => array()));
$tester = new CommandTester($command);
$tester->execute(array(), array('decorated' => false));
$this->assertSame(<<<TXT
Messenger
=========
command_bus
-----------
[WARNING] No handled message found in bus "command_bus".
query_bus
---------
[WARNING] No handled message found in bus "query_bus".
TXT
, $tester->getDisplay(true)
);
}
/**
* @expectedException \Symfony\Component\Console\Exception\RuntimeException
* @expectedExceptionMessage Bus "unknown_bus" does not exist. Known buses are command_bus, query_bus.
*/
public function testExceptionOnUnknownBusArgument()
{
$command = new DebugCommand(array('command_bus' => array(), 'query_bus' => array()));
$tester = new CommandTester($command);
$tester->execute(array('bus' => 'unknown_bus'), array('decorated' => false));
}
}

View File

@ -14,30 +14,38 @@ namespace Symfony\Component\Messenger\Tests\DependencyInjection;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass; use Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass;
use Symfony\Component\DependencyInjection\Compiler\ResolveClassPass;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ServiceLocator; use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\Messenger\Command\ConsumeMessagesCommand; use Symfony\Component\Messenger\Command\ConsumeMessagesCommand;
use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Command\DebugCommand;
use Symfony\Component\Messenger\Transport\AmqpExt\AmqpReceiver;
use Symfony\Component\Messenger\Transport\AmqpExt\AmqpSender;
use Symfony\Component\Messenger\Handler\Locator\ContainerHandlerLocator;
use Symfony\Component\Messenger\DataCollector\MessengerDataCollector; use Symfony\Component\Messenger\DataCollector\MessengerDataCollector;
use Symfony\Component\Messenger\DependencyInjection\MessengerPass; use Symfony\Component\Messenger\DependencyInjection\MessengerPass;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Handler\ChainHandler; use Symfony\Component\Messenger\Handler\ChainHandler;
use Symfony\Component\Messenger\Handler\MessageSubscriberInterface; use Symfony\Component\Messenger\Handler\MessageSubscriberInterface;
use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Messenger\Middleware\AllowNoHandlerMiddleware; use Symfony\Component\Messenger\Middleware\AllowNoHandlerMiddleware;
use Symfony\Component\Messenger\Middleware\HandleMessageMiddleware;
use Symfony\Component\Messenger\Middleware\MiddlewareInterface; use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
use Symfony\Component\Messenger\Tests\Fixtures\DummyCommand;
use Symfony\Component\Messenger\Tests\Fixtures\DummyCommandHandler;
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage; use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
use Symfony\Component\Messenger\Tests\Fixtures\DummyQuery;
use Symfony\Component\Messenger\Tests\Fixtures\DummyQueryHandler;
use Symfony\Component\Messenger\Tests\Fixtures\MultipleBusesMessage;
use Symfony\Component\Messenger\Tests\Fixtures\MultipleBusesMessageHandler;
use Symfony\Component\Messenger\Tests\Fixtures\SecondMessage; use Symfony\Component\Messenger\Tests\Fixtures\SecondMessage;
use Symfony\Component\Messenger\Transport\AmqpExt\AmqpReceiver;
use Symfony\Component\Messenger\Transport\AmqpExt\AmqpSender;
use Symfony\Component\Messenger\Transport\ReceiverInterface; use Symfony\Component\Messenger\Transport\ReceiverInterface;
class MessengerPassTest extends TestCase class MessengerPassTest extends TestCase
{ {
public function testProcess() public function testProcess()
{ {
$container = $this->getContainerBuilder(); $container = $this->getContainerBuilder($busId = 'message_bus');
$container $container
->register(DummyHandler::class, DummyHandler::class) ->register(DummyHandler::class, DummyHandler::class)
->addTag('messenger.message_handler') ->addTag('messenger.message_handler')
@ -55,7 +63,7 @@ class MessengerPassTest extends TestCase
$this->assertFalse($container->hasDefinition('messenger.middleware.debug.logging')); $this->assertFalse($container->hasDefinition('messenger.middleware.debug.logging'));
$handlerLocatorDefinition = $container->getDefinition($container->getDefinition('messenger.handler_resolver')->getArgument(0)); $handlerLocatorDefinition = $container->getDefinition($container->getDefinition("$busId.messenger.handler_resolver")->getArgument(0));
$this->assertSame(ServiceLocator::class, $handlerLocatorDefinition->getClass()); $this->assertSame(ServiceLocator::class, $handlerLocatorDefinition->getClass());
$this->assertEquals( $this->assertEquals(
array( array(
@ -71,9 +79,68 @@ class MessengerPassTest extends TestCase
); );
} }
public function testProcessHandlersByBus()
{
$container = $this->getContainerBuilder($commandBusId = 'command_bus');
$container->register($queryBusId = 'query_bus', MessageBusInterface::class)->setArgument(0, array())->addTag('messenger.bus');
$container->register('messenger.middleware.call_message_handler', HandleMessageMiddleware::class)
->addArgument(null)
->setAbstract(true)
;
$middlewares = array(array('id' => 'call_message_handler'));
$container->setParameter($commandBusId.'.middleware', $middlewares);
$container->setParameter($queryBusId.'.middleware', $middlewares);
$container->register(DummyCommandHandler::class)->addTag('messenger.message_handler', array('bus' => $commandBusId));
$container->register(DummyQueryHandler::class)->addTag('messenger.message_handler', array('bus' => $queryBusId));
$container->register(MultipleBusesMessageHandler::class)
->addTag('messenger.message_handler', array('bus' => $commandBusId))
->addTag('messenger.message_handler', array('bus' => $queryBusId))
;
(new ResolveClassPass())->process($container);
(new MessengerPass())->process($container);
$commandBusHandlerLocatorDefinition = $container->getDefinition($container->getDefinition("$commandBusId.messenger.handler_resolver")->getArgument(0));
$this->assertSame(ServiceLocator::class, $commandBusHandlerLocatorDefinition->getClass());
$this->assertEquals(
array(
'handler.'.DummyCommand::class => new ServiceClosureArgument(new Reference(DummyCommandHandler::class)),
'handler.'.MultipleBusesMessage::class => new ServiceClosureArgument(new Reference(MultipleBusesMessageHandler::class)),
),
$commandBusHandlerLocatorDefinition->getArgument(0)
);
$queryBusHandlerLocatorDefinition = $container->getDefinition($container->getDefinition("$queryBusId.messenger.handler_resolver")->getArgument(0));
$this->assertSame(ServiceLocator::class, $queryBusHandlerLocatorDefinition->getClass());
$this->assertEquals(
array(
'handler.'.DummyQuery::class => new ServiceClosureArgument(new Reference(DummyQueryHandler::class)),
'handler.'.MultipleBusesMessage::class => new ServiceClosureArgument(new Reference(MultipleBusesMessageHandler::class)),
),
$queryBusHandlerLocatorDefinition->getArgument(0)
);
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
* @expectedExceptionMessage Invalid handler service "Symfony\Component\Messenger\Tests\Fixtures\DummyCommandHandler": bus "unknown_bus" specified on the tag "messenger.message_handler" does not exist (known ones are: command_bus).
*/
public function testProcessTagWithUnknownBus()
{
$container = $this->getContainerBuilder($commandBusId = 'command_bus');
$container->register(DummyCommandHandler::class)->addTag('messenger.message_handler', array('bus' => 'unknown_bus'));
(new ResolveClassPass())->process($container);
(new MessengerPass())->process($container);
}
public function testGetClassesFromTheHandlerSubscriberInterface() public function testGetClassesFromTheHandlerSubscriberInterface()
{ {
$container = $this->getContainerBuilder(); $container = $this->getContainerBuilder($busId = 'message_bus');
$container $container
->register(HandlerWithMultipleMessages::class, HandlerWithMultipleMessages::class) ->register(HandlerWithMultipleMessages::class, HandlerWithMultipleMessages::class)
->addTag('messenger.message_handler') ->addTag('messenger.message_handler')
@ -85,7 +152,7 @@ class MessengerPassTest extends TestCase
(new MessengerPass())->process($container); (new MessengerPass())->process($container);
$handlerLocatorDefinition = $container->getDefinition($container->getDefinition('messenger.handler_resolver')->getArgument(0)); $handlerLocatorDefinition = $container->getDefinition($container->getDefinition("$busId.messenger.handler_resolver")->getArgument(0));
$handlerMapping = $handlerLocatorDefinition->getArgument(0); $handlerMapping = $handlerLocatorDefinition->getArgument(0);
$this->assertArrayHasKey('handler.'.DummyMessage::class, $handlerMapping); $this->assertArrayHasKey('handler.'.DummyMessage::class, $handlerMapping);
@ -101,7 +168,7 @@ class MessengerPassTest extends TestCase
public function testGetClassesAndMethodsAndPrioritiesFromTheSubscriber() public function testGetClassesAndMethodsAndPrioritiesFromTheSubscriber()
{ {
$container = $this->getContainerBuilder(); $container = $this->getContainerBuilder($busId = 'message_bus');
$container $container
->register(HandlerMappingMethods::class, HandlerMappingMethods::class) ->register(HandlerMappingMethods::class, HandlerMappingMethods::class)
->addTag('messenger.message_handler') ->addTag('messenger.message_handler')
@ -113,7 +180,7 @@ class MessengerPassTest extends TestCase
(new MessengerPass())->process($container); (new MessengerPass())->process($container);
$handlerLocatorDefinition = $container->getDefinition($container->getDefinition('messenger.handler_resolver')->getArgument(0)); $handlerLocatorDefinition = $container->getDefinition($container->getDefinition("$busId.messenger.handler_resolver")->getArgument(0));
$handlerMapping = $handlerLocatorDefinition->getArgument(0); $handlerMapping = $handlerLocatorDefinition->getArgument(0);
$this->assertArrayHasKey('handler.'.DummyMessage::class, $handlerMapping); $this->assertArrayHasKey('handler.'.DummyMessage::class, $handlerMapping);
@ -138,6 +205,7 @@ class MessengerPassTest extends TestCase
public function testThrowsExceptionIfTheHandlerMethodDoesNotExist() public function testThrowsExceptionIfTheHandlerMethodDoesNotExist()
{ {
$container = $this->getContainerBuilder(); $container = $this->getContainerBuilder();
$container->register('message_bus', MessageBusInterface::class)->addTag('messenger.bus');
$container $container
->register(HandlerMappingWithNonExistentMethod::class, HandlerMappingWithNonExistentMethod::class) ->register(HandlerMappingWithNonExistentMethod::class, HandlerMappingWithNonExistentMethod::class)
->addTag('messenger.message_handler') ->addTag('messenger.message_handler')
@ -149,6 +217,7 @@ class MessengerPassTest extends TestCase
public function testItRegistersReceivers() public function testItRegistersReceivers()
{ {
$container = $this->getContainerBuilder(); $container = $this->getContainerBuilder();
$container->register('message_bus', MessageBusInterface::class)->addTag('messenger.bus');
$container->register(AmqpReceiver::class, AmqpReceiver::class)->addTag('messenger.receiver', array('alias' => 'amqp')); $container->register(AmqpReceiver::class, AmqpReceiver::class)->addTag('messenger.receiver', array('alias' => 'amqp'));
(new MessengerPass())->process($container); (new MessengerPass())->process($container);
@ -159,6 +228,7 @@ class MessengerPassTest extends TestCase
public function testItRegistersReceiversWithoutTagName() public function testItRegistersReceiversWithoutTagName()
{ {
$container = $this->getContainerBuilder(); $container = $this->getContainerBuilder();
$container->register('message_bus', MessageBusInterface::class)->addTag('messenger.bus');
$container->register(AmqpReceiver::class, AmqpReceiver::class)->addTag('messenger.receiver'); $container->register(AmqpReceiver::class, AmqpReceiver::class)->addTag('messenger.receiver');
(new MessengerPass())->process($container); (new MessengerPass())->process($container);
@ -326,9 +396,8 @@ class MessengerPassTest extends TestCase
{ {
$dataCollector = $this->getMockBuilder(MessengerDataCollector::class)->getMock(); $dataCollector = $this->getMockBuilder(MessengerDataCollector::class)->getMock();
$container = $this->getContainerBuilder(); $container = $this->getContainerBuilder($fooBusId = 'messenger.bus.foo');
$container->register('messenger.data_collector', $dataCollector); $container->register('messenger.data_collector', $dataCollector);
$container->register($fooBusId = 'messenger.bus.foo', MessageBusInterface::class)->addTag('messenger.bus');
$container->setParameter('kernel.debug', true); $container->setParameter('kernel.debug', true);
(new MessengerPass())->process($container); (new MessengerPass())->process($container);
@ -340,8 +409,7 @@ class MessengerPassTest extends TestCase
public function testRegistersMiddlewareFromServices() public function testRegistersMiddlewareFromServices()
{ {
$container = $this->getContainerBuilder(); $container = $this->getContainerBuilder($fooBusId = 'messenger.bus.foo');
$container->register($fooBusId = 'messenger.bus.foo', MessageBusInterface::class)->setArgument(0, array())->addTag('messenger.bus');
$container->register('messenger.middleware.allow_no_handler', AllowNoHandlerMiddleware::class)->setAbstract(true); $container->register('messenger.middleware.allow_no_handler', AllowNoHandlerMiddleware::class)->setAbstract(true);
$container->register('middleware_with_factory', UselessMiddleware::class)->addArgument('some_default')->setAbstract(true); $container->register('middleware_with_factory', UselessMiddleware::class)->addArgument('some_default')->setAbstract(true);
$container->register('middleware_with_factory_using_default', UselessMiddleware::class)->addArgument('some_default')->setAbstract(true); $container->register('middleware_with_factory_using_default', UselessMiddleware::class)->addArgument('some_default')->setAbstract(true);
@ -388,8 +456,7 @@ class MessengerPassTest extends TestCase
*/ */
public function testCannotRegistersAnUndefinedMiddleware() public function testCannotRegistersAnUndefinedMiddleware()
{ {
$container = $this->getContainerBuilder(); $container = $this->getContainerBuilder($fooBusId = 'messenger.bus.foo');
$container->register($fooBusId = 'messenger.bus.foo', MessageBusInterface::class)->setArgument(0, array())->addTag('messenger.bus');
$container->setParameter($middlewareParameter = $fooBusId.'.middleware', array( $container->setParameter($middlewareParameter = $fooBusId.'.middleware', array(
array('id' => 'not_defined_middleware', 'arguments' => array()), array('id' => 'not_defined_middleware', 'arguments' => array()),
)); ));
@ -403,9 +470,8 @@ class MessengerPassTest extends TestCase
*/ */
public function testMiddlewareFactoryDefinitionMustBeAbstract() public function testMiddlewareFactoryDefinitionMustBeAbstract()
{ {
$container = $this->getContainerBuilder(); $container = $this->getContainerBuilder($fooBusId = 'messenger.bus.foo');
$container->register('not_an_abstract_definition', UselessMiddleware::class); $container->register('not_an_abstract_definition', UselessMiddleware::class);
$container->register($fooBusId = 'messenger.bus.foo', MessageBusInterface::class)->setArgument(0, array())->addTag('messenger.bus', array('name' => 'foo'));
$container->setParameter($middlewareParameter = $fooBusId.'.middleware', array( $container->setParameter($middlewareParameter = $fooBusId.'.middleware', array(
array('id' => 'not_an_abstract_definition', 'arguments' => array('foo')), array('id' => 'not_an_abstract_definition', 'arguments' => array('foo')),
)); ));
@ -413,18 +479,58 @@ class MessengerPassTest extends TestCase
(new MessengerPass())->process($container); (new MessengerPass())->process($container);
} }
private function getContainerBuilder(): ContainerBuilder public function testItRegistersTheDebugCommand()
{
$container = $this->getContainerBuilder($commandBusId = 'command_bus');
$container->register($queryBusId = 'query_bus', MessageBusInterface::class)->setArgument(0, array())->addTag('messenger.bus');
$container->register($emptyBus = 'empty_bus', MessageBusInterface::class)->setArgument(0, array())->addTag('messenger.bus');
$container->register('messenger.middleware.call_message_handler', HandleMessageMiddleware::class)
->addArgument(null)
->setAbstract(true)
;
$container->register('console.command.messenger_debug', DebugCommand::class)->addArgument(array());
$middlewares = array(array('id' => 'call_message_handler'));
$container->setParameter($commandBusId.'.middleware', $middlewares);
$container->setParameter($queryBusId.'.middleware', $middlewares);
$container->register(DummyCommandHandler::class)->addTag('messenger.message_handler', array('bus' => $commandBusId));
$container->register(DummyQueryHandler::class)->addTag('messenger.message_handler', array('bus' => $queryBusId));
$container->register(MultipleBusesMessageHandler::class)
->addTag('messenger.message_handler', array('bus' => $commandBusId))
->addTag('messenger.message_handler', array('bus' => $queryBusId))
;
(new ResolveClassPass())->process($container);
(new MessengerPass())->process($container);
$this->assertEquals(array(
$commandBusId => array(
DummyCommand::class => array(DummyCommandHandler::class),
MultipleBusesMessage::class => array(MultipleBusesMessageHandler::class),
),
$queryBusId => array(
DummyQuery::class => array(DummyQueryHandler::class),
MultipleBusesMessage::class => array(MultipleBusesMessageHandler::class),
),
$emptyBus => array(),
), $container->getDefinition('console.command.messenger_debug')->getArgument(0));
}
private function getContainerBuilder(string $busId = 'message_bus'): ContainerBuilder
{ {
$container = new ContainerBuilder(); $container = new ContainerBuilder();
$container->setParameter('kernel.debug', true); $container->setParameter('kernel.debug', true);
$container $container->register($busId, MessageBusInterface::class)->addTag('messenger.bus')->setArgument(0, array());
->register('messenger.sender_locator', ServiceLocator::class) if ('message_bus' !== $busId) {
->addArgument(new Reference('service_container')) $container->setAlias('message_bus', $busId);
; }
$container $container
->register('messenger.handler_resolver', ContainerHandlerLocator::class) ->register('messenger.sender_locator', ServiceLocator::class)
->addArgument(new Reference('service_container')) ->addArgument(new Reference('service_container'))
; ;

View File

@ -0,0 +1,16 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Messenger\Tests\Fixtures;
class DummyCommand
{
}

View File

@ -0,0 +1,19 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Messenger\Tests\Fixtures;
class DummyCommandHandler
{
public function __invoke(DummyCommand $command)
{
}
}

View File

@ -0,0 +1,16 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Messenger\Tests\Fixtures;
class DummyQuery
{
}

View File

@ -0,0 +1,19 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Messenger\Tests\Fixtures;
class DummyQueryHandler
{
public function __invoke(DummyQuery $query)
{
}
}

View File

@ -0,0 +1,16 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Messenger\Tests\Fixtures;
class MultipleBusesMessage
{
}

View File

@ -0,0 +1,19 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Messenger\Tests\Fixtures;
class MultipleBusesMessageHandler
{
public function __invoke(MultipleBusesMessage $message)
{
}
}