Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 07e6bc7

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

File tree

5 files changed

+194
-9
lines changed
Filter options

5 files changed

+194
-9
lines changed

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+3Lines changed: 3 additions & 0 deletions
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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php
+28-9Lines changed: 28 additions & 9 deletions
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)
+21Lines changed: 21 additions & 0 deletions
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+
}
+40Lines changed: 40 additions & 0 deletions
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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php
+102Lines changed: 102 additions & 0 deletions
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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.