[Messenger] added a SenderLocator decoupled from ContainerInterface

This commit is contained in:
Fabien Potencier 2018-09-08 09:19:24 +02:00
parent f2f4cd82c3
commit e658e155aa
9 changed files with 216 additions and 94 deletions

View File

@ -8,7 +8,7 @@
<defaults public="false" />
<!-- Asynchronous -->
<service id="messenger.asynchronous.routing.sender_locator" class="Symfony\Component\Messenger\Asynchronous\Routing\SenderLocator">
<service id="messenger.asynchronous.routing.sender_locator" class="Symfony\Component\Messenger\Asynchronous\Routing\ContainerSenderLocator">
<argument type="service" id="messenger.sender_locator" />
<argument type="collection" /> <!-- Message to sender ID mapping -->
</service>

View File

@ -11,7 +11,7 @@
namespace Symfony\Component\Messenger\Asynchronous\Middleware;
use Symfony\Component\Messenger\Asynchronous\Routing\SenderLocator;
use Symfony\Component\Messenger\Asynchronous\Routing\AbstractSenderLocator;
use Symfony\Component\Messenger\Asynchronous\Routing\SenderLocatorInterface;
use Symfony\Component\Messenger\Asynchronous\Transport\ReceivedMessage;
use Symfony\Component\Messenger\Envelope;
@ -60,6 +60,6 @@ class SendMessageMiddleware implements MiddlewareInterface, EnvelopeAwareInterfa
private function mustSendAndHandle($message): bool
{
return (bool) SenderLocator::getValueFromMessageRouting($this->messagesToSendAndHandleMapping, $message);
return (bool) AbstractSenderLocator::getValueFromMessageRouting($this->messagesToSendAndHandleMapping, $message);
}
}

View File

@ -0,0 +1,38 @@
<?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\Asynchronous\Routing;
/**
* @author Samuel Roze <samuel.roze@gmail.com>
*
* @internal
*/
abstract class AbstractSenderLocator implements SenderLocatorInterface
{
public static function getValueFromMessageRouting(array $mapping, $message)
{
if (isset($mapping[\get_class($message)])) {
return $mapping[\get_class($message)];
}
if ($parentsMapping = array_intersect_key($mapping, class_parents($message))) {
return current($parentsMapping);
}
if ($interfaceMapping = array_intersect_key($mapping, class_implements($message))) {
return current($interfaceMapping);
}
if (isset($mapping['*'])) {
return $mapping['*'];
}
return null;
}
}

View File

@ -0,0 +1,40 @@
<?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\Asynchronous\Routing;
use Psr\Container\ContainerInterface;
use Symfony\Component\Messenger\Transport\SenderInterface;
/**
* @author Samuel Roze <samuel.roze@gmail.com>
*/
class ContainerSenderLocator extends AbstractSenderLocator
{
private $senderServiceLocator;
private $messageToSenderIdMapping;
public function __construct(ContainerInterface $senderServiceLocator, array $messageToSenderIdMapping)
{
$this->senderServiceLocator = $senderServiceLocator;
$this->messageToSenderIdMapping = $messageToSenderIdMapping;
}
/**
* {@inheritdoc}
*/
public function getSenderForMessage($message): ?SenderInterface
{
$senderId = self::getValueFromMessageRouting($this->messageToSenderIdMapping, $message);
return $senderId ? $this->senderServiceLocator->get($senderId) : null;
}
}

View File

@ -11,21 +11,19 @@
namespace Symfony\Component\Messenger\Asynchronous\Routing;
use Psr\Container\ContainerInterface;
use Symfony\Component\Messenger\Exception\RuntimeException;
use Symfony\Component\Messenger\Transport\SenderInterface;
/**
* @author Samuel Roze <samuel.roze@gmail.com>
* @author Fabien Potencier <fabien@symfony.com>
*/
class SenderLocator implements SenderLocatorInterface
class SenderLocator extends AbstractSenderLocator
{
private $senderServiceLocator;
private $messageToSenderIdMapping;
private $messageToSenderMapping;
public function __construct(ContainerInterface $senderServiceLocator, array $messageToSenderIdMapping)
public function __construct(array $messageToSenderMapping)
{
$this->senderServiceLocator = $senderServiceLocator;
$this->messageToSenderIdMapping = $messageToSenderIdMapping;
$this->messageToSenderMapping = $messageToSenderMapping;
}
/**
@ -33,34 +31,15 @@ class SenderLocator implements SenderLocatorInterface
*/
public function getSenderForMessage($message): ?SenderInterface
{
$senderId = $this->getSenderId($message);
return $senderId ? $this->senderServiceLocator->get($senderId) : null;
}
private function getSenderId($message): ?string
{
return self::getValueFromMessageRouting($this->messageToSenderIdMapping, $message);
}
/**
* @internal
*/
public static function getValueFromMessageRouting(array $mapping, $message)
{
if (isset($mapping[\get_class($message)])) {
return $mapping[\get_class($message)];
}
if ($parentsMapping = array_intersect_key($mapping, class_parents($message))) {
return current($parentsMapping);
}
if ($interfaceMapping = array_intersect_key($mapping, class_implements($message))) {
return current($interfaceMapping);
}
if (isset($mapping['*'])) {
return $mapping['*'];
$sender = self::getValueFromMessageRouting($this->messageToSenderMapping, $message);
if (null === $sender) {
return null;
}
return null;
if (!$sender instanceof SenderInterface) {
throw new RuntimeException(sprintf('The sender instance provided for message "%s" should be of type "%s" but got "%s".', \get_class($message), SenderInterface::class, \is_object($sender) ? \get_class($sender) : \gettype($sender)));
}
return $sender;
}
}

View File

@ -4,6 +4,9 @@ CHANGELOG
4.2.0
-----
* [BC BREAK] `SenderLocator` has been renamed to `ContainerSenderLocator`
Be careful as there is still a `SenderLocator` class, but it does not rely on a `ContainerInterface` to find senders.
Instead, it accepts the sender instance itself instead of its identifier in the container.
* [BC BREAK] `MessageSubscriberInterface::getHandledMessages()` return value has changed. The value of an array item
needs to be an associative array or the method name.
* `ValidationMiddleware::handle()` and `SendMessageMiddleware::handle()` now require an `Envelope` object

View File

@ -0,0 +1,19 @@
<?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\Exception;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}

View File

@ -0,0 +1,91 @@
<?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\Asynchronous\Routing;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\Messenger\Asynchronous\Routing\ContainerSenderLocator;
use Symfony\Component\Messenger\Tests\Fixtures\ChildDummyMessage;
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessageInterface;
use Symfony\Component\Messenger\Tests\Fixtures\SecondMessage;
use Symfony\Component\Messenger\Transport\SenderInterface;
class ContainerSenderLocatorTest extends TestCase
{
public function testItReturnsTheSenderBasedOnTheMessageClass()
{
$sender = $this->getMockBuilder(SenderInterface::class)->getMock();
$container = new Container();
$container->set('my_amqp_sender', $sender);
$locator = new ContainerSenderLocator($container, array(
DummyMessage::class => 'my_amqp_sender',
));
$this->assertSame($sender, $locator->getSenderForMessage(new DummyMessage('Hello')));
$this->assertNull($locator->getSenderForMessage(new SecondMessage()));
}
public function testItReturnsTheSenderBasedOnTheMessageParentClass()
{
$container = new Container();
$sender = $this->getMockBuilder(SenderInterface::class)->getMock();
$container->set('my_amqp_sender', $sender);
$apiSender = $this->getMockBuilder(SenderInterface::class)->getMock();
$container->set('my_api_sender', $apiSender);
$locator = new ContainerSenderLocator($container, array(
DummyMessageInterface::class => 'my_api_sender',
DummyMessage::class => 'my_amqp_sender',
));
$this->assertSame($sender, $locator->getSenderForMessage(new ChildDummyMessage('Hello')));
$this->assertNull($locator->getSenderForMessage(new SecondMessage()));
}
public function testItReturnsTheSenderBasedOnTheMessageInterface()
{
$container = new Container();
$sender = $this->getMockBuilder(SenderInterface::class)->getMock();
$container->set('my_amqp_sender', $sender);
$locator = new ContainerSenderLocator($container, array(
DummyMessageInterface::class => 'my_amqp_sender',
));
$this->assertSame($sender, $locator->getSenderForMessage(new DummyMessage('Hello')));
$this->assertNull($locator->getSenderForMessage(new SecondMessage()));
}
public function testItSupportsAWildcardInsteadOfTheMessageClass()
{
$container = new Container();
$sender = $this->getMockBuilder(SenderInterface::class)->getMock();
$container->set('my_amqp_sender', $sender);
$apiSender = $this->getMockBuilder(SenderInterface::class)->getMock();
$container->set('my_api_sender', $apiSender);
$locator = new ContainerSenderLocator($container, array(
DummyMessage::class => 'my_amqp_sender',
'*' => 'my_api_sender',
));
$this->assertSame($sender, $locator->getSenderForMessage(new DummyMessage('Hello')));
$this->assertSame($apiSender, $locator->getSenderForMessage(new SecondMessage()));
}
}

View File

@ -12,11 +12,9 @@
namespace Symfony\Component\Messenger\Tests\Asynchronous\Routing;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\Messenger\Asynchronous\Routing\SenderLocator;
use Symfony\Component\Messenger\Tests\Fixtures\ChildDummyMessage;
use Symfony\Component\Messenger\Exception\RuntimeException;
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessageInterface;
use Symfony\Component\Messenger\Tests\Fixtures\SecondMessage;
use Symfony\Component\Messenger\Transport\SenderInterface;
@ -25,67 +23,21 @@ class SenderLocatorTest extends TestCase
public function testItReturnsTheSenderBasedOnTheMessageClass()
{
$sender = $this->getMockBuilder(SenderInterface::class)->getMock();
$container = new Container();
$container->set('my_amqp_sender', $sender);
$locator = new SenderLocator($container, array(
DummyMessage::class => 'my_amqp_sender',
$locator = new SenderLocator(array(
DummyMessage::class => $sender,
));
$this->assertSame($sender, $locator->getSenderForMessage(new DummyMessage('Hello')));
$this->assertNull($locator->getSenderForMessage(new SecondMessage()));
}
public function testItReturnsTheSenderBasedOnTheMessageParentClass()
public function testItThrowsExceptionIfConfigurationIsWrong()
{
$container = new Container();
$sender = $this->getMockBuilder(SenderInterface::class)->getMock();
$container->set('my_amqp_sender', $sender);
$apiSender = $this->getMockBuilder(SenderInterface::class)->getMock();
$container->set('my_api_sender', $apiSender);
$locator = new SenderLocator($container, array(
DummyMessageInterface::class => 'my_api_sender',
DummyMessage::class => 'my_amqp_sender',
$locator = new SenderLocator(array(
DummyMessage::class => 'amqp',
));
$this->assertSame($sender, $locator->getSenderForMessage(new ChildDummyMessage('Hello')));
$this->assertNull($locator->getSenderForMessage(new SecondMessage()));
}
public function testItReturnsTheSenderBasedOnTheMessageInterface()
{
$container = new Container();
$sender = $this->getMockBuilder(SenderInterface::class)->getMock();
$container->set('my_amqp_sender', $sender);
$locator = new SenderLocator($container, array(
DummyMessageInterface::class => 'my_amqp_sender',
));
$this->assertSame($sender, $locator->getSenderForMessage(new DummyMessage('Hello')));
$this->assertNull($locator->getSenderForMessage(new SecondMessage()));
}
public function testItSupportsAWildcardInsteadOfTheMessageClass()
{
$container = new Container();
$sender = $this->getMockBuilder(SenderInterface::class)->getMock();
$container->set('my_amqp_sender', $sender);
$apiSender = $this->getMockBuilder(SenderInterface::class)->getMock();
$container->set('my_api_sender', $apiSender);
$locator = new SenderLocator($container, array(
DummyMessage::class => 'my_amqp_sender',
'*' => 'my_api_sender',
));
$this->assertSame($sender, $locator->getSenderForMessage(new DummyMessage('Hello')));
$this->assertSame($apiSender, $locator->getSenderForMessage(new SecondMessage()));
$this->expectException(RuntimeException::class);
$locator->getSenderForMessage(new DummyMessage('Hello'));
}
}