From e658e155aa92c1b52c59773769e4f344ce09c3a2 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 8 Sep 2018 09:19:24 +0200 Subject: [PATCH] [Messenger] added a SenderLocator decoupled from ContainerInterface --- .../Resources/config/messenger.xml | 2 +- .../Middleware/SendMessageMiddleware.php | 4 +- .../Routing/AbstractSenderLocator.php | 38 ++++++++ .../Routing/ContainerSenderLocator.php | 40 ++++++++ .../Asynchronous/Routing/SenderLocator.php | 47 +++------- src/Symfony/Component/Messenger/CHANGELOG.md | 3 + .../Messenger/Exception/RuntimeException.php | 19 ++++ .../Routing/ContainerSenderLocatorTest.php | 91 +++++++++++++++++++ .../Routing/SenderLocatorTest.php | 64 ++----------- 9 files changed, 215 insertions(+), 93 deletions(-) create mode 100644 src/Symfony/Component/Messenger/Asynchronous/Routing/AbstractSenderLocator.php create mode 100644 src/Symfony/Component/Messenger/Asynchronous/Routing/ContainerSenderLocator.php create mode 100644 src/Symfony/Component/Messenger/Exception/RuntimeException.php create mode 100644 src/Symfony/Component/Messenger/Tests/Asynchronous/Routing/ContainerSenderLocatorTest.php diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.xml index 0b09978d33870..ca5dc6b95ebca 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.xml @@ -8,7 +8,7 @@ - + diff --git a/src/Symfony/Component/Messenger/Asynchronous/Middleware/SendMessageMiddleware.php b/src/Symfony/Component/Messenger/Asynchronous/Middleware/SendMessageMiddleware.php index 5fc0ec87cfb5f..9e7dc9baef42f 100644 --- a/src/Symfony/Component/Messenger/Asynchronous/Middleware/SendMessageMiddleware.php +++ b/src/Symfony/Component/Messenger/Asynchronous/Middleware/SendMessageMiddleware.php @@ -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 @@ public function handle($envelope, callable $next) private function mustSendAndHandle($message): bool { - return (bool) SenderLocator::getValueFromMessageRouting($this->messagesToSendAndHandleMapping, $message); + return (bool) AbstractSenderLocator::getValueFromMessageRouting($this->messagesToSendAndHandleMapping, $message); } } diff --git a/src/Symfony/Component/Messenger/Asynchronous/Routing/AbstractSenderLocator.php b/src/Symfony/Component/Messenger/Asynchronous/Routing/AbstractSenderLocator.php new file mode 100644 index 0000000000000..eafdd1de9f007 --- /dev/null +++ b/src/Symfony/Component/Messenger/Asynchronous/Routing/AbstractSenderLocator.php @@ -0,0 +1,38 @@ + + * + * 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 + * + * @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; + } +} diff --git a/src/Symfony/Component/Messenger/Asynchronous/Routing/ContainerSenderLocator.php b/src/Symfony/Component/Messenger/Asynchronous/Routing/ContainerSenderLocator.php new file mode 100644 index 0000000000000..ef338bd5066b8 --- /dev/null +++ b/src/Symfony/Component/Messenger/Asynchronous/Routing/ContainerSenderLocator.php @@ -0,0 +1,40 @@ + + * + * 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 + */ +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; + } +} diff --git a/src/Symfony/Component/Messenger/Asynchronous/Routing/SenderLocator.php b/src/Symfony/Component/Messenger/Asynchronous/Routing/SenderLocator.php index e85453691bd92..8c8b6b8a75a2f 100644 --- a/src/Symfony/Component/Messenger/Asynchronous/Routing/SenderLocator.php +++ b/src/Symfony/Component/Messenger/Asynchronous/Routing/SenderLocator.php @@ -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 + * @author Fabien Potencier */ -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 @@ public function __construct(ContainerInterface $senderServiceLocator, array $mes */ 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)]; + $sender = self::getValueFromMessageRouting($this->messageToSenderMapping, $message); + if (null === $sender) { + return null; } - 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['*']; + + 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 null; + return $sender; } } diff --git a/src/Symfony/Component/Messenger/CHANGELOG.md b/src/Symfony/Component/Messenger/CHANGELOG.md index 934c8e2fe671d..540226a6b6338 100644 --- a/src/Symfony/Component/Messenger/CHANGELOG.md +++ b/src/Symfony/Component/Messenger/CHANGELOG.md @@ -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 diff --git a/src/Symfony/Component/Messenger/Exception/RuntimeException.php b/src/Symfony/Component/Messenger/Exception/RuntimeException.php new file mode 100644 index 0000000000000..2d6c7b36779a3 --- /dev/null +++ b/src/Symfony/Component/Messenger/Exception/RuntimeException.php @@ -0,0 +1,19 @@ + + * + * 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 + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/src/Symfony/Component/Messenger/Tests/Asynchronous/Routing/ContainerSenderLocatorTest.php b/src/Symfony/Component/Messenger/Tests/Asynchronous/Routing/ContainerSenderLocatorTest.php new file mode 100644 index 0000000000000..29eac0f05617e --- /dev/null +++ b/src/Symfony/Component/Messenger/Tests/Asynchronous/Routing/ContainerSenderLocatorTest.php @@ -0,0 +1,91 @@ + + * + * 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())); + } +} diff --git a/src/Symfony/Component/Messenger/Tests/Asynchronous/Routing/SenderLocatorTest.php b/src/Symfony/Component/Messenger/Tests/Asynchronous/Routing/SenderLocatorTest.php index 370ffd3cddeeb..f39a31617a7ab 100644 --- a/src/Symfony/Component/Messenger/Tests/Asynchronous/Routing/SenderLocatorTest.php +++ b/src/Symfony/Component/Messenger/Tests/Asynchronous/Routing/SenderLocatorTest.php @@ -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', - )); - - $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 SenderLocator($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 SenderLocator($container, array( - DummyMessageInterface::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 testItSupportsAWildcardInsteadOfTheMessageClass() + 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( - DummyMessage::class => 'my_amqp_sender', - '*' => 'my_api_sender', + $locator = new SenderLocator(array( + DummyMessage::class => 'amqp', )); - $this->assertSame($sender, $locator->getSenderForMessage(new DummyMessage('Hello'))); - $this->assertSame($apiSender, $locator->getSenderForMessage(new SecondMessage())); + $this->expectException(RuntimeException::class); + $locator->getSenderForMessage(new DummyMessage('Hello')); } }