8000 [Messenger] Compile time errors fixes and tweaks · symfony/symfony@0d569f6 · GitHub
[go: up one dir, main page]

Skip to content

Commit 0d569f6

Browse files
committed
[Messenger] Compile time errors fixes and tweaks
1 parent 54305d6 commit 0d569f6

File tree

2 files changed

+186
-13
lines changed

2 files changed

+186
-13
lines changed

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

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,12 @@ private function registerHandlers(ContainerBuilder $container)
6161

6262
foreach ($container->findTaggedServiceIds($this->handlerTag, true) as $serviceId => $tags) {
6363
foreach ($tags as $tag) {
64-
$handles = $tag['handles'] ?? $this->guessHandledClass($container, $serviceId);
64+
$handles = $tag['handles'] ?? $this->guessHandledClass($r = $container->getReflectionClass($container->getDefinition($serviceId)->getClass()), $serviceId);
6565

6666
if (!class_exists($handles)) {
67-
$messageClassLocation = isset($tag['handles']) ? 'declared in your tag attribute "handles"' : 'declared in `__invoke` function';
67+
$messageClassLocation = isset($tag['handles']) ? 'declared in your tag attribute "handles"' : sprintf('used as argument type in method "%s::__invoke()"', $r->getName());
6868

69-
throw new RuntimeException(sprintf('The message class "%s" %s of service "%s" does not exist.', $messageClassLocation, $handles, $serviceId));
69+
throw new RuntimeException(sprintf('Invalid service "%s": message class "%s" %s does not exist.', $serviceId, $handles, $messageClassLocation));
7070
}
7171

7272
$priority = $tag['priority'] ?? 0;
@@ -102,27 +102,25 @@ private function registerHandlers(ContainerBuilder $container)
102102
$handlerResolver->replaceArgument(0, ServiceLocatorTagPass::register($container, $handlersLocatorMapping));
103103
}
104104

105-
private function guessHandledClass(ContainerBuilder $container, string $serviceId): string
105+
private function guessHandledClass(\ReflectionClass $handlerClass, string $serviceId): string
106106
{
107-
$reflection = $container->getReflectionClass($container->getDefinition($serviceId)->getClass());
108-
109107
try {
110-
$method = $reflection->getMethod('__invoke');
108+
$method = $handlerClass->getMethod('__invoke');
111109
} catch (\ReflectionException $e) {
112-
throw new RuntimeException(sprintf('Service "%s" should have an `__invoke` function.', $serviceId));
110+
throw new RuntimeException(sprintf('Invalid service "%s": class "%s" must have an "__invoke()" method.', $serviceId, $handlerClass->getName()));
113111
}
114112

115113
$parameters = $method->getParameters();
116114
if (1 !== count($parameters)) {
117-
throw new RuntimeException(sprintf('`__invoke` function of service "%s" must have exactly one parameter.', $serviceId));
115+
throw new RuntimeException(sprintf('Invalid service "%s": method "%s::__invoke()" must have exactly one argument corresponding to the message it handles.', $serviceId, $handlerClass->getName())
116+
);
118117
}
119118

120-
$parameter = $parameters[0];
121-
if (null === $parameter->getClass()) {
122-
throw new RuntimeException(sprintf('The parameter of `__invoke` function of service "%s" must type hint the message class it handles.', $serviceId));
119+
if (!$parameters[0]->hasType()) {
120+
throw new RuntimeException(sprintf('Invalid service "%s": argument of method "%s::__invoke()" must have a type-hint corresponding to the message class it handles.', $serviceId, $handlerClass->getName()));
123121
}
124122

125-
return $parameter->getClass()->getName();
123+
return $parameters[0]->getType();
126124
}
127125

128126
private function registerReceivers(ContainerBuilder $container)
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
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\Tests\DependencyInjection;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
16+
use Symfony\Component\DependencyInjection\ContainerBuilder;
17+
use Symfony\Component\DependencyInjection\Reference;
18+
use Symfony\Component\DependencyInjection\ServiceLocator;
19+
use Symfony\Component\Messenger\ContainerHandlerLocator;
20+
use Symfony\Component\Messenger\DependencyInjection\MessengerPass;
21+
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
22+
use Symfony\Component\Messenger\Transport\ReceiverInterface;
23+
24+
class MessengerPassTest extends TestCase
25+
{
26+
public function testProcess()
27+
{
28+
$container = $this->getContainerBuilder();
29+
$container
30+
->register(DummyHandler::class, DummyHandler::class)
31+
->addTag('message_handler')
32+
;
33+
$container
34+
->register(DummyReceiver::class, DummyReceiver::class)
35+
->addTag('messenger.receiver')
36+
;
37+
38+
(new MessengerPass())->process($container);
39+
40+
$handlerLocatorDefinition = $container->getDefinition($container->getDefinition('messenger.handler_resolver')->getArgument(0));
41+
$this->assertSame(ServiceLocator::class, $handlerLocatorDefinition->getClass());
42+
$this->assertEquals(
43+
array('handler.'.DummyMessage::class => new ServiceClosureArgument(new Reference(DummyHandler::class))),
44+
$handlerLocatorDefinition->getArgument(0)
45+
);
46+
47+
$this->assertEquals(
48+
array(DummyReceiver::class => new Reference(DummyReceiver::class)),
49+
$container->getDefinition('messenger.receiver_locator')->getArgument(0)
50+
);
51+
}
52+
53+
/**
54+
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
55+
* @expectedExceptionMessage Invalid 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.
56+
*/
57+
public function testUndefinedMessageClassForHandler()
58+
{
59+
$container = $this->getContainerBuilder();
60+
$container
61+
->register(UndefinedMessageHandler::class, UndefinedMessageHandler::class)
62+
->addTag('message_handler')
63+
;
64+
65+
(new MessengerPass())->process($container);
66+
}
67+
68+
/**
69+
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
70+
* @expectedExceptionMessage Invalid service "Symfony\Component\Messenger\Tests\DependencyInjection\NotInvokableHandler": class "Symfony\Component\Messenger\Tests\DependencyInjection\NotInvokableHandler" must have an "__invoke()" method.
71+
*/
72+
public function testNotInvokableHandler()
73+
{
74+
$container = $this->getContainerBuilder();
75+
$container
76+
->register(NotInvokableHandler::class, NotInvokableHandler::class)
77+
->addTag('message_handler')
78+
;
79+
80+
(new MessengerPass())->process($container);
81+
}
82+
83+
/**
84+
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
85+
* @expectedExceptionMessage Invalid service "Symfony\Component\Messenger\Tests\DependencyInjection\MissingArgumentHandler": method "Symfony\Component\Messenger\Tests\DependencyInjection\MissingArgumentHandler::__invoke()" must have exactly one argument corresponding to the message it handles.
86+
*/
87+
public function testMissingArgumentHandler()
88+
{
89+
$container = $this->getContainerBuilder();
90+
$container
91+
->register(MissingArgumentHandler::class, MissingArgumentHandler::class)
92+
->addTag('message_handler')
93+
;
94+
95+
(new MessengerPass())->process($container);
96+
}
97+
98+
/**
99+
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
100+
* @expectedExceptionMessage Invalid service "Symfony\Component\Messenger\Tests\DependencyInjection\MissingArgumentTypeHandler": argument of method "Symfony\Component\Messenger\Tests\DependencyInjection\MissingArgumentTypeHandler::__invoke()" must have a type-hint corresponding to the message class it handles.
101+
*/
102+
public function testMissingArgumentTypeHandler()
103+
{
104+
$container = $this->getContainerBuilder();
105+
$container
106+
->register(MissingArgumentTypeHandler::class, MissingArgumentTypeHandler::class)
107+
->addTag('message_handler')
108+
;
109+
110+
(new MessengerPass())->process($container);
111+
}
112+
113+
private function getContainerBuilder(): ContainerBuilder
114+
{
115+
$container = new ContainerBuilder();
116+
$container->setParameter('kernel.debug', true);
117+
118+
$container
119+
->register('message_bus', ContainerHandlerLocator::class)
120+
;
121+
122+
$container
123+
->register('messenger.handler_resolver', ContainerHandlerLocator::class)
124+
->addArgument(new Reference('service_container'))
125+
;
126+
127+
$container->register('messenger.receiver_locator', ServiceLocator::class)
128+
->addArgument(new Reference('service_container'))
129+
;
130+
131+
return $container;
132+
}
133+
}
134+
135+
class DummyHandler
136+
{
137+
public function __invoke(DummyMessage $message): void
138+
{
139+
}
140+
}
141+
142+
class DummyReceiver implements ReceiverInterface
143+
{
144+
public function receive(): iterable
145+
{
146+
for ($i = 0; $i < 3; ++$i) {
147+
yield new DummyMessage("Dummy $i");
148+
}
149+
}
150+
}
151+
152+
class UndefinedMessageHandler
153+
{
154+
public function __invoke(UndefinedMessage $message)
155+
{
156+
}
157+
}
158+
159+
class NotInvokableHandler
160+
{
161+
}
162+
163+
class MissingArgumentHandler
164+
{
165+
public function __invoke()
166+
{
167+
}
168+
}
169+
170+
class MissingArgumentTypeHandler
171+
{
172+
public function __invoke($message)
173+
{
174+
}
175+
}

0 commit comments

Comments
 (0)
0