Skip to content

[Messenger][DX] Uses custom method names for handlers #27034

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 11, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -74,27 +74,55 @@ public function process(ContainerBuilder $container)

private function registerHandlers(ContainerBuilder $container)
{
$definitions = array();
$handlersByMessage = array();

foreach ($container->findTaggedServiceIds($this->handlerTag, true) as $serviceId => $tags) {
foreach ($tags as $tag) {
$handles = isset($tag['handles']) ? array($tag['handles']) : $this->guessHandledClasses($r = $container->getReflectionClass($container->getDefinition($serviceId)->getClass()), $serviceId);
$r = $container->getReflectionClass($container->getDefinition($serviceId)->getClass());

if (isset($tag['handles'])) {
$handles = isset($tag['method']) ? array($tag['handles'] => $tag['method']) : array($tag['handles']);
} else {
$handles = $this->guessHandledClasses($r, $serviceId);
}

$priority = $tag['priority'] ?? 0;

foreach ($handles as $messageClass) {
foreach ($handles as $messageClass => $method) {
if (\is_int($messageClass)) {
$messageClass = $method;
$method = '__invoke';
}

if (\is_array($messageClass)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could be else if here, right?

Copy link
Contributor Author

@sroze sroze May 7, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed with elseif.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fabpot actually can't because we are setting $messagePriority as well.

$messagePriority = $messageClass[1];
$messageClass = $messageClass[0];
} else {
$messagePriority = $priority;
}

if (!class_exists($messageClass)) {
$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());
if (\is_array($method)) {
$messagePriority = $method[1];
$method = $method[0];
}

if (!\class_exists($messageClass)) {
$messageClassLocation = isset($tag['handles']) ? 'declared in your tag attribute "handles"' : $r->implementsInterface(MessageHandlerInterface::class) ? sprintf('returned by method "%s::getHandledMessages()"', $r->getName()) : sprintf('used as argument type in method "%s::%s()"', $r->getName(), $method);

throw new RuntimeException(sprintf('Invalid handler service "%s": message class "%s" %s does not exist.', $serviceId, $messageClass, $messageClassLocation));
}

if (!$r->hasMethod($method)) {
throw new RuntimeException(sprintf('Invalid handler service "%s": method "%s::%s()" does not exist.', $serviceId, $r->getName(), $method));
}

if ('__invoke' !== $method) {
$wrapperDefinition = (new Definition('callable'))->addArgument(array(new Reference($serviceId), $method))->setFactory('Closure::fromCallable');

$definitions[$serviceId = '.messenger.method_on_object_wrapper.'.ContainerBuilder::hash($messageClass.':'.$messagePriority.':'.$serviceId.':'.$method)] = $wrapperDefinition;
}

$handlersByMessage[$messageClass][$messagePriority][] = new Reference($serviceId);
}
}
Expand All @@ -105,7 +133,6 @@ private function registerHandlers(ContainerBuilder $container)
$handlersByMessage[$message] = array_merge(...$handlersByMessage[$message]);
}

$definitions = array();
$handlersLocatorMapping = array();
foreach ($handlersByMessage as $message => $handlers) {
if (1 === \count($handlers)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,14 @@ interface MessageSubscriberInterface extends MessageHandlerInterface
* [SecondMessage::class, -10],
* ];
*
* The `__invoke` method of the handler will be called as usual with the message to handle.
* It can also specify a method and/or a priority per message:
*
* @return array
* return [
* FirstMessage::class => 'firstMessageMethod',
* SecondMessage::class => ['secondMessageMethod', 20],
* ];
*
* The `__invoke` method of the handler will be called as usual with the message to handle.
*/
public static function getHandledMessages(): array;
public static function getHandledMessages(): iterable;
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,53 @@ public function testGetClassesFromTheHandlerSubscriberInterface()
$this->assertEquals(array(new Reference(PrioritizedHandler::class), new Reference(HandlerWithMultipleMessages::class)), $definition->getArgument(0));
}

public function testGetClassesAndMethodsAndPrioritiesFromTheSubscriber()
{
$container = $this->getContainerBuilder();
$container
->register(HandlerMappingMethods::class, HandlerMappingMethods::class)
->addTag('messenger.message_handler')
;
$container
->register(PrioritizedHandler::class, PrioritizedHandler::class)
->addTag('messenger.message_handler')
;

(new MessengerPass())->process($container);

$handlerLocatorDefinition = $container->getDefinition($container->getDefinition('messenger.handler_resolver')->getArgument(0));
$handlerMapping = $handlerLocatorDefinition->getArgument(0);

$this->assertArrayHasKey('handler.'.DummyMessage::class, $handlerMapping);
$this->assertArrayHasKey('handler.'.SecondMessage::class, $handlerMapping);

$dummyHandlerReference = (string) $handlerMapping['handler.'.DummyMessage::class]->getValues()[0];
$dummyHandlerDefinition = $container->getDefinition($dummyHandlerReference);
$this->assertSame('callable', $dummyHandlerDefinition->getClass());
$this->assertEquals(array(new Reference(HandlerMappingMethods::class), 'dummyMethod'), $dummyHandlerDefinition->getArgument(0));
$this->assertSame(array('Closure', 'fromCallable'), $dummyHandlerDefinition->getFactory());

$secondHandlerReference = (string) $handlerMapping['handler.'.SecondMessage::class]->getValues()[0];
$secondHandlerDefinition = $container->getDefinition($secondHandlerReference);
$this->assertSame(ChainHandler::class, $secondHandlerDefinition->getClass());
$this->assertEquals(new Reference(PrioritizedHandler::class), $secondHandlerDefinition->getArgument(0)[1]);
}

/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
* @expectedExceptionMessage Invalid handler service "Symfony\Component\Messenger\Tests\DependencyInjection\HandlerMappingWithNonExistentMethod": method "Symfony\Component\Messenger\Tests\DependencyInjection\HandlerMappingWithNonExistentMethod::dummyMethod()" does not exist.
*/
public function testThrowsExceptionIfTheHandlerMethodDoesNotExist()
{
$container = $this->getContainerBuilder();
$container
->register(HandlerMappingWithNonExistentMethod::class, HandlerMappingWithNonExistentMethod::class)
->addTag('messenger.message_handler')
;

(new MessengerPass())->process($container);
}

public function testItRegistersReceivers()
{
$container = $this->getContainerBuilder();
Expand Down Expand Up @@ -397,7 +444,7 @@ public function __invoke(UndefinedMessage $message)

class UndefinedMessageHandlerViaInterface implements MessageSubscriberInterface
{
public static function getHandledMessages(): array
public static function getHandledMessages(): iterable
{
return array(UndefinedMessage::class);
}
Expand Down Expand Up @@ -434,31 +481,72 @@ public function __invoke(string $message)

class HandlerWithMultipleMessages implements MessageSubscriberInterface
{
public static function getHandledMessages(): array
public static function getHandledMessages(): iterable
{
return array(
DummyMessage::class,
SecondMessage::class,
);
}

public function __invoke()
{
}
}

class PrioritizedHandler implements MessageSubscriberInterface
{
public static function getHandledMessages(): array
public static function getHandledMessages(): iterable
{
return array(
array(SecondMessage::class, 10),
);
}

public function __invoke()
{
}
}

class HandlerMappingMethods implements MessageSubscriberInterface
{
public static function getHandledMessages(): iterable
{
return array(
DummyMessage::class => 'dummyMethod',
SecondMessage::class => array('secondMessage', 20),
);
}

public function dummyMethod()
{
}

public function secondMessage()
{
}
}

class HandlerMappingWithNonExistentMethod implements MessageSubscriberInterface
{
public static function getHandledMessages(): iterable
{
return array(
DummyMessage::class => 'dummyMethod',
);
}
}

class HandleNoMessageHandler implements MessageSubscriberInterface
{
public static function getHandledMessages(): array
public static function getHandledMessages(): iterable
{
return array();
}

public function __invoke()
{
}
}

class UselessMiddleware implements MiddlewareInterface
Expand Down