Skip to content

Commit 07e6bc7

Browse files
committed
[Messenger] Add a MessageHandlerInterface (multiple messages + auto-configuration)
1 parent 5b68f69 commit 07e6bc7

File tree

5 files changed

+194
-9
lines changed

5 files changed

+194
-9
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

+3
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
use Symfony\Component\Lock\LockInterface;
6161
use Symfony\Component\Lock\Store\StoreFactory;
6262
use Symfony\Component\Lock\StoreInterface;
63+
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
6364
use Symfony\Component\Messenger\Transport\ReceiverInterface;
6465
use Symfony\Component\Messenger\Transport\SenderInterface;
6566
use Symfony\Component\PropertyAccess\PropertyAccessor;
@@ -347,6 +348,8 @@ public function load(array $configs, ContainerBuilder $container)
347348
->addTag('messenger.receiver');
348349
$container->registerForAutoconfiguration(SenderInterface::class)
349350
->addTag('messenger.sender');
351+
$container->registerForAutoconfiguration(MessageHandlerInterface::class)
352+
->addTag('messenger.message_handler');
350353

351354
if (!$container->getParameter('kernel.debug')) {
352355
// remove tagged iterator argument for resource checkers

src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php

+28-9
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
2020
use Symfony\Component\DependencyInjection\Reference;
2121
use Symfony\Component\Messenger\Handler\ChainHandler;
22+
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
23+
use Symfony\Component\Messenger\Handler\MessageSubscriberInterface;
2224

2325
/**
2426
* @author Samuel Roze <samuel.roze@gmail.com>
@@ -67,16 +69,25 @@ private function registerHandlers(ContainerBuilder $container)
6769

6870
foreach ($container->findTaggedServiceIds($this->handlerTag, true) as $serviceId => $tags) {
6971
foreach ($tags as $tag) {
70-
$handles = $tag['handles'] ?? $this->guessHandledClass($r = $container->getReflectionClass($container->getParameterBag()->resolveValue($container->getDefinition($serviceId)->getClass())), $serviceId);
72+
$handles = $tag['handles'] ?? $this->guessHandledClasses($r = $container->getReflectionClass($container->getDefinition($serviceId)->getClass()), $serviceId);
73+
$priority = $tag['priority'] ?? 0;
7174

72-
if (!class_exists($handles)) {
73-
$messageClassLocation = isset($tag['handles']) ? 'declared in your tag attribute "handles"' : sprintf('used as argument type in method "%s::__invoke()"', $r->getName());
75+
foreach ($handles as $messageClass) {
76+
if (is_array($messageClass)) {
77+
$messagePriority = $messageClass[1];
78+
$messageClass = $messageClass[0];
79+
} else {
80+
$messagePriority = $priority;
81+
}
7482

75-
throw new RuntimeException(sprintf('Invalid handler service "%s": message class "%s" %s does not exist.', $serviceId, $handles, $messageClassLocation));
76-
}
83+
if (!class_exists($messageClass)) {
84+
$messageClassLocation = isset($tag['handles']) ? 'declared in your tag attribute "handles"' : sprintf($r->implementsInterface(MessageHandlerInterface::class) ? 'returned by method "%s::getHandledMessages()"' : 'used as argument type in method "%s::__invoke()"', $r->getName());
7785

78-
$priority = $tag['priority'] ?? 0;
79-
$handlersByMessage[$handles][$priority][] = new Reference($serviceId);
86+
throw new RuntimeException(sprintf('Invalid handler service "%s": message class "%s" %s does not exist.', $serviceId, $messageClass, $messageClassLocation));
87+
}
88+
89+
$handlersByMessage[$messageClass][$messagePriority][] = new Reference($serviceId);
90+
}
8091
}
8192
}
8293

@@ -108,8 +119,16 @@ private function registerHandlers(ContainerBuilder $container)
108119
$handlerResolver->replaceArgument(0, ServiceLocatorTagPass::register($container, $handlersLocatorMapping));
109120
}
110121

111-
private function guessHandledClass(\ReflectionClass $handlerClass, string $serviceId): string
122+
private function guessHandledClasses(\ReflectionClass $handlerClass, string $serviceId): array
112123
{
124+
if ($handlerClass->implementsInterface(MessageSubscriberInterface::class)) {
125+
if (!$handledMessages = $handlerClass->getName()::getHandledMessages()) {
126+
throw new RuntimeException(sprintf('Invalid handler service "%s": method "%s::getHandledMessages()" must return one or more messages.', $serviceId, $handlerClass->getName()));
127+
}
128+
129+
return $handledMessages;
130+
}
131+
113132
try {
114133
$method = $handlerClass->getMethod('__invoke');
115134
} catch (\ReflectionException $e) {
@@ -129,7 +148,7 @@ private function guessHandledClass(\ReflectionClass $handlerClass, string $servi
129148
throw new RuntimeException(sprintf('Invalid handler service "%s": type-hint of argument "$%s" in method "%s::__invoke()" must be a class , "%s" given.', $serviceId, $parameters[0]->getName(), $handlerClass->getName(), $type));
130149
}
131150

132-
return $parameters[0]->getType();
151+
return array((string) $parameters[0]->getType());
133152
}
134153

135154
private function registerReceivers(ContainerBuilder $container)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Messenger\Handler;
13+
14+
/**
15+
* Handlers can implement this interface.
16+
*
17+
* @author Samuel Roze <samuel.roze@gmail.com>
18+
*/
19+
interface MessageHandlerInterface
20+
{
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Messenger\Handler;
13+
14+
/**
15+
* Handlers can implement this interface to handle multiple messages.
16+
*
17+
* @author Samuel Roze <samuel.roze@gmail.com>
18+
*/
19+
interface MessageSubscriberInterface extends MessageHandlerInterface
20+
{
21+
/**
22+
* Return a list of messages to be handled.
23+
*
24+
* It returns a list of messages like in the following example:
25+
*
26+
* return [MyMessage::class];
27+
*
28+
* It can also change the priority per classes.
29+
*
30+
* return [
31+
* [FirstMessage::class, 0],
32+
* [SecondMessage::class, -10],
33+
* ];
34+
*
35+
* The `__invoke` method of the handler will be called as usual with the message to handle.
36+
*
37+
* @return array
38+
*/
39+
public static function getHandledMessages(): array;
40+
}

src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php

+102
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@
1818
use Symfony\Component\DependencyInjection\ServiceLocator;
1919
use Symfony\Component\Messenger\ContainerHandlerLocator;
2020
use Symfony\Component\Messenger\DependencyInjection\MessengerPass;
21+
use Symfony\Component\Messenger\Handler\ChainHandler;
22+
use Symfony\Component\Messenger\Handler\MessageSubscriberInterface;
2123
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
24+
use Symfony\Component\Messenger\Tests\Fixtures\SecondMessage;
2225
use Symfony\Component\Messenger\Transport\ReceiverInterface;
2326

2427
class MessengerPassTest extends TestCase
@@ -50,6 +53,34 @@ public function testProcess()
5053
);
5154
}
5255

56+
public function testGetClassesFromTheHandlerSubscriberInterface()
57+
{
58+
$container = $this->getContainerBuilder();
59+
$container
60+
->register(HandlerWithMultipleMessages::class, HandlerWithMultipleMessages::class)
61+
->addTag('messenger.message_handler')
62+
;
63+
$container
64+
->register(PrioritizedHandler::class, PrioritizedHandler::class)
65+
->addTag('messenger.message_handler')
66+
;
67+
68+
(new MessengerPass())->process($container);
69+
70+
$handlerLocatorDefinition = $container->getDefinition($container->getDefinition('messenger.handler_resolver')->getArgument(0));
71+
$handlerMapping = $handlerLocatorDefinition->getArgument(0);
72+
73+
$this->assertArrayHasKey('handler.'.DummyMessage::class, $handlerMapping);
74+
$this->assertEquals(new ServiceClosureArgument(new Reference(HandlerWithMultipleMessages::class)), $handlerMapping['handler.'.DummyMessage::class]);
75+
76+
$this->assertArrayHasKey('handler.'.SecondMessage::class, $handlerMapping);
77+
$handlerReference = (string) $handlerMapping['handler.'.SecondMessage::class]->getValues()[0];
78+
$definition = $container->getDefinition($handlerReference);
79+
80+
$this->assertSame(ChainHandler::class, $definition->getClass());
81+
$this->assertEquals(array(new Reference(PrioritizedHandler::class), new Reference(HandlerWithMultipleMessages::class)), $definition->getArgument(0));
82+
}
83+
5384
/**
5485
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
5586
* @expectedExceptionMessage Invalid handler service "Symfony\Component\Messenger\Tests\DependencyInjection\UndefinedMessageHandler": message class "Symfony\Component\Messenger\Tests\DependencyInjection\UndefinedMessage" used as argument type in method "Symfony\Component\Messenger\Tests\DependencyInjection\UndefinedMessageHandler::__invoke()" does not exist.
@@ -65,6 +96,21 @@ public function testUndefinedMessageClassForHandler()
6596
(new MessengerPass())->process($container);
6697
}
6798

99+
/**
100+
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
101+
* @expectedExceptionMessage Invalid handler service "Symfony\Component\Messenger\Tests\DependencyInjection\UndefinedMessageHandlerViaInterface": message class "Symfony\Component\Messenger\Tests\DependencyInjection\UndefinedMessage" returned by method "Symfony\Component\Messenger\Tests\DependencyInjection\UndefinedMessageHandlerViaInterface::getHandledMessages()" does not exist.
102+
*/
103+
public function testUndefinedMessageClassForHandlerViaInterface()
104+
{
105+
$container = $this->getContainerBuilder();
106+
$container
107+
->register(UndefinedMessageHandlerViaInterface::class, UndefinedMessageHandlerViaInterface::class)
108+
->addTag('messenger.message_handler')
109+
;
110+
111+
(new MessengerPass())->process($container);
112+
}
113+
68114
/**
69115
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
70116
* @expectedExceptionMessage Invalid handler service "Symfony\Component\Messenger\Tests\DependencyInjection\NotInvokableHandler": class "Symfony\Component\Messenger\Tests\DependencyInjection\NotInvokableHandler" must have an "__invoke()" method.
@@ -125,6 +171,21 @@ public function testBuiltinArgumentTypeHandler()
125171
(new MessengerPass())->process($container);
126172
}
127173

174+
/**
175+
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
176+
* @expectedExceptionMessage Invalid handler service "Symfony\Component\Messenger\Tests\DependencyInjection\HandleNoMessageHandler": method "Symfony\Component\Messenger\Tests\DependencyInjection\HandleNoMessageHandler::getHandledMessages()" must return one or more messages.
177+
*/
178+
public function testNeedsToHandleAtLeastOneMessage()
179+
{
180+
$container = $this->getContainerBuilder();
181+
$container
182+
->register(HandleNoMessageHandler::class, HandleNoMessageHandler::class)
183+
->addTag('messenger.message_handler')
184+
;
185+
186+
(new MessengerPass())->process($container);
187+
}
188+
128189
private function getContainerBuilder(): ContainerBuilder
129190
{
130191
$container = new ContainerBuilder();
@@ -168,6 +229,18 @@ public function __invoke(UndefinedMessage $message)
168229
}
169230
}
170231

232+
class UndefinedMessageHandlerViaInterface implements MessageSubscriberInterface
233+
{
234+
public static function getHandledMessages(): array
235+
{
236+
return array(UndefinedMessage::class);
237+
}
238+
239+
public function __invoke()
240+
{
241+
}
242+
}
243+
171244
class NotInvokableHandler
172245
{
173246
}
@@ -192,3 +265,32 @@ public function __invoke(string $message)
192265
{
193266
}
194267
}
268+
269+
class HandlerWithMultipleMessages implements MessageSubscriberInterface
270+
{
271+
public static function getHandledMessages(): array
272+
{
273+
return array(
274+
DummyMessage::class,
275+
SecondMessage::class,
276+
);
277+
}
278+
}
279+
280+
class PrioritizedHandler implements MessageSubscriberInterface
281+
{
282+
public static function getHandledMessages(): array
283+
{
284+
return array(
285+
array(SecondMessage::class, 10),
286+
);
287+
}
288+
}
289+
290+
class HandleNoMessageHandler implements MessageSubscriberInterface
291+
{
292+
public static function getHandledMessages(): array
293+
{
294+
return array();
295+
}
296+
}

0 commit comments

Comments
 (0)