Skip to content

Commit

Permalink
minor #26672 [Messenger] Compile-time errors fixes and tweaks (chalasr)
Browse files Browse the repository at this point in the history
This PR was merged into the 4.1-dev branch.

Discussion
----------

[Messenger] Compile-time errors fixes and tweaks

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no (master only)
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | n/a
| License       | MIT
| Doc PR        | n/a

Commits
-------

2889acf [Messenger] Compile time errors fixes and tweaks
  • Loading branch information
fabpot committed Mar 29, 2018
2 parents 9b6ed69 + 2889acf commit ecb629a
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 14 deletions.
Expand Up @@ -67,12 +67,12 @@ private function registerHandlers(ContainerBuilder $container)

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

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

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

$priority = $tag['priority'] ?? 0;
Expand Down Expand Up @@ -108,27 +108,28 @@ private function registerHandlers(ContainerBuilder $container)
$handlerResolver->replaceArgument(0, ServiceLocatorTagPass::register($container, $handlersLocatorMapping));
}

private function guessHandledClass(ContainerBuilder $container, string $serviceId): string
private function guessHandledClass(\ReflectionClass $handlerClass, string $serviceId): string
{
$reflection = $container->getReflectionClass($container->getDefinition($serviceId)->getClass());

try {
$method = $reflection->getMethod('__invoke');
$method = $handlerClass->getMethod('__invoke');
} catch (\ReflectionException $e) {
throw new RuntimeException(sprintf('Service "%s" should have an `__invoke` function.', $serviceId));
throw new RuntimeException(sprintf('Invalid handler service "%s": class "%s" must have an "__invoke()" method.', $serviceId, $handlerClass->getName()));
}

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

if (!$type = $parameters[0]->getType()) {
throw new RuntimeException(sprintf('Invalid handler service "%s": argument "$%s" of method "%s::__invoke()" must have a type-hint corresponding to the message class it handles.', $serviceId, $parameters[0]->getName(), $handlerClass->getName()));
}

$parameter = $parameters[0];
if (null === $parameter->getClass()) {
throw new RuntimeException(sprintf('The parameter of `__invoke` function of service "%s" must type hint the message class it handles.', $serviceId));
if ($type->isBuiltin()) {
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));
}

return $parameter->getClass()->getName();
return $parameters[0]->getType();
}

private function registerReceivers(ContainerBuilder $container)
Expand Down
@@ -0,0 +1,194 @@
<?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\DependencyInjection;

use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\Messenger\ContainerHandlerLocator;
use Symfony\Component\Messenger\DependencyInjection\MessengerPass;
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
use Symfony\Component\Messenger\Transport\ReceiverInterface;

class MessengerPassTest extends TestCase
{
public function testProcess()
{
$container = $this->getContainerBuilder();
$container
->register(DummyHandler::class, DummyHandler::class)
->addTag('messenger.message_handler')
;
$container
->register(DummyReceiver::class, DummyReceiver::class)
->addTag('messenger.receiver')
;

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

$handlerLocatorDefinition = $container->getDefinition($container->getDefinition('messenger.handler_resolver')->getArgument(0));
$this->assertSame(ServiceLocator::class, $handlerLocatorDefinition->getClass());
$this->assertEquals(
array('handler.'.DummyMessage::class => new ServiceClosureArgument(new Reference(DummyHandler::class))),
$handlerLocatorDefinition->getArgument(0)
);

$this->assertEquals(
array(DummyReceiver::class => new Reference(DummyReceiver::class)),
$container->getDefinition('messenger.receiver_locator')->getArgument(0)
);
}

/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
* @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.
*/
public function testUndefinedMessageClassForHandler()
{
$container = $this->getContainerBuilder();
$container
->register(UndefinedMessageHandler::class, UndefinedMessageHandler::class)
->addTag('messenger.message_handler')
;

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

/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
* @expectedExceptionMessage Invalid handler service "Symfony\Component\Messenger\Tests\DependencyInjection\NotInvokableHandler": class "Symfony\Component\Messenger\Tests\DependencyInjection\NotInvokableHandler" must have an "__invoke()" method.
*/
public function testNotInvokableHandler()
{
$container = $this->getContainerBuilder();
$container
->register(NotInvokableHandler::class, NotInvokableHandler::class)
->addTag('messenger.message_handler')
;

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

/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
* @expectedExceptionMessage Invalid handler 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.
*/
public function testMissingArgumentHandler()
{
$container = $this->getContainerBuilder();
$container
->register(MissingArgumentHandler::class, MissingArgumentHandler::class)
->addTag('messenger.message_handler')
;

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

/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
* @expectedExceptionMessage Invalid handler service "Symfony\Component\Messenger\Tests\DependencyInjection\MissingArgumentTypeHandler": argument "$message" of method "Symfony\Component\Messenger\Tests\DependencyInjection\MissingArgumentTypeHandler::__invoke()" must have a type-hint corresponding to the message class it handles.
*/
public function testMissingArgumentTypeHandler()
{
$container = $this->getContainerBuilder();
$container
->register(MissingArgumentTypeHandler::class, MissingArgumentTypeHandler::class)
->addTag('messenger.message_handler')
;

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

/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
* @expectedExceptionMessage Invalid handler service "Symfony\Component\Messenger\Tests\DependencyInjection\BuiltinArgumentTypeHandler": type-hint of argument "$message" in method "Symfony\Component\Messenger\Tests\DependencyInjection\BuiltinArgumentTypeHandler::__invoke()" must be a class , "string" given.
*/
public function testBuiltinArgumentTypeHandler()
{
$container = $this->getContainerBuilder();
$container
->register(BuiltinArgumentTypeHandler::class, BuiltinArgumentTypeHandler::class)
->addTag('messenger.message_handler')
;

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

private function getContainerBuilder(): ContainerBuilder
{
$container = new ContainerBuilder();
$container->setParameter('kernel.debug', true);
$container->register('message_bus', ContainerHandlerLocator::class);

$container
->register('messenger.handler_resolver', ContainerHandlerLocator::class)
->addArgument(new Reference('service_container'))
;

$container->register('messenger.receiver_locator', ServiceLocator::class)
->addArgument(new Reference('service_container'))
;

return $container;
}
}

class DummyHandler
{
public function __invoke(DummyMessage $message): void
{
}
}

class DummyReceiver implements ReceiverInterface
{
public function receive(): iterable
{
for ($i = 0; $i < 3; ++$i) {
yield new DummyMessage("Dummy $i");
}
}
}

class UndefinedMessageHandler
{
public function __invoke(UndefinedMessage $message)
{
}
}

class NotInvokableHandler
{
}

class MissingArgumentHandler
{
public function __invoke()
{
}
}

class MissingArgumentTypeHandler
{
public function __invoke($message)
{
}
}

class BuiltinArgumentTypeHandler
{
public function __invoke(string $message)
{
}
}
2 changes: 1 addition & 1 deletion src/Symfony/Component/Messenger/composer.json
Expand Up @@ -20,7 +20,7 @@
},
"require-dev": {
"symfony/serializer": "~3.4|~4.0",
"symfony/dependency-injection": "~3.4|~4.0",
"symfony/dependency-injection": "~3.4.6|~4.0",
"symfony/property-access": "~3.4|~4.0"
},
"suggest": {
Expand Down

0 comments on commit ecb629a

Please sign in to comment.