From d65284239fc73158f86eb3198bf9d1095eadf659 Mon Sep 17 00:00:00 2001 From: Pierre Rineau Date: Mon, 24 Jun 2024 13:12:00 +0200 Subject: [PATCH] feature #57506 [Messenger] introduce AsMessage attribute for message routing --- .../Messenger/Attribute/AsMessage.php | 29 +++++++++++ src/Symfony/Component/Messenger/CHANGELOG.md | 1 + .../Fixtures/DummyMessageWithAttribute.php | 19 +++++++ .../Transport/Sender/SendersLocatorTest.php | 52 +++++++++++++++++++ .../Transport/Sender/SendersLocator.php | 30 +++++++++++ 5 files changed, 131 insertions(+) create mode 100644 src/Symfony/Component/Messenger/Attribute/AsMessage.php create mode 100644 src/Symfony/Component/Messenger/Tests/Fixtures/DummyMessageWithAttribute.php diff --git a/src/Symfony/Component/Messenger/Attribute/AsMessage.php b/src/Symfony/Component/Messenger/Attribute/AsMessage.php new file mode 100644 index 0000000000000..bc60ec032bff9 --- /dev/null +++ b/src/Symfony/Component/Messenger/Attribute/AsMessage.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Messenger\Attribute; + +/** + * Attribute for configuring message routing. + * + * @author Pierre Rineau pierre.rineau@processus.org> + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] +class AsMessage +{ + public function __construct( + /** + * Name of the transports to which the message should be routed. + */ + public null|string|array $transport = null, + ) { + } +} diff --git a/src/Symfony/Component/Messenger/CHANGELOG.md b/src/Symfony/Component/Messenger/CHANGELOG.md index d8f4705562eec..bdb161a7f3048 100644 --- a/src/Symfony/Component/Messenger/CHANGELOG.md +++ b/src/Symfony/Component/Messenger/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 7.2 --- + * Add `#[AsMessage]` attribute with `$transport` parameter for message routing * Add `--format` option to the `messenger:stats` command 7.1 diff --git a/src/Symfony/Component/Messenger/Tests/Fixtures/DummyMessageWithAttribute.php b/src/Symfony/Component/Messenger/Tests/Fixtures/DummyMessageWithAttribute.php new file mode 100644 index 0000000000000..ceb85833b478b --- /dev/null +++ b/src/Symfony/Component/Messenger/Tests/Fixtures/DummyMessageWithAttribute.php @@ -0,0 +1,19 @@ +message; + } +} diff --git a/src/Symfony/Component/Messenger/Tests/Transport/Sender/SendersLocatorTest.php b/src/Symfony/Component/Messenger/Tests/Transport/Sender/SendersLocatorTest.php index 3aafa82f73d73..ef81920af8917 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/Sender/SendersLocatorTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/Sender/SendersLocatorTest.php @@ -17,6 +17,8 @@ use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Stamp\TransportNamesStamp; use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage; +use Symfony\Component\Messenger\Tests\Fixtures\DummyMessageInterface; +use Symfony\Component\Messenger\Tests\Fixtures\DummyMessageWithAttribute; use Symfony\Component\Messenger\Tests\Fixtures\SecondMessage; use Symfony\Component\Messenger\Transport\Sender\SenderInterface; use Symfony\Component\Messenger\Transport\Sender\SendersLocator; @@ -53,6 +55,56 @@ public function testItReturnsTheSenderBasedOnTransportNamesStamp() $this->assertSame([], iterator_to_array($locator->getSenders(new Envelope(new SecondMessage())))); } + public function testItReturnsTheSenderBasedOnAsMessageAttribute() + { + $firstSender = $this->createMock(SenderInterface::class); + $secondSender = $this->createMock(SenderInterface::class); + $otherSender = $this->createMock(SenderInterface::class); + $sendersLocator = $this->createContainer([ + 'first_sender' => $firstSender, + 'second_sender' => $secondSender, + 'other_sender' => $otherSender, + ]); + $locator = new SendersLocator([], $sendersLocator); + + $this->assertSame(['first_sender' => $firstSender, 'second_sender' => $secondSender], iterator_to_array($locator->getSenders(new Envelope(new DummyMessageWithAttribute('a'))))); + $this->assertSame([], iterator_to_array($locator->getSenders(new Envelope(new SecondMessage())))); + } + + public function testAsMessageAttributeIsOverridenByTransportNamesStamp() + { + $firstSender = $this->createMock(SenderInterface::class); + $secondSender = $this->createMock(SenderInterface::class); + $otherSender = $this->createMock(SenderInterface::class); + $sendersLocator = $this->createContainer([ + 'first_sender' => $firstSender, + 'second_sender' => $secondSender, + 'other_sender' => $otherSender, + ]); + $locator = new SendersLocator([], $sendersLocator); + + $this->assertSame(['other_sender' => $otherSender], iterator_to_array($locator->getSenders(new Envelope(new DummyMessageWithAttribute('a'), [new TransportNamesStamp(['other_sender'])])))); + $this->assertSame([], iterator_to_array($locator->getSenders(new Envelope(new SecondMessage())))); + } + + public function testAsMessageAttributeIsOverridenByUserConfiguration() + { + $firstSender = $this->createMock(SenderInterface::class); + $secondSender = $this->createMock(SenderInterface::class); + $otherSender = $this->createMock(SenderInterface::class); + $sendersLocator = $this->createContainer([ + 'first_sender' => $firstSender, + 'second_sender' => $secondSender, + 'other_sender' => $otherSender, + ]); + $locator = new SendersLocator([ + DummyMessageInterface::class => ['other_sender'], + ], $sendersLocator); + + $this->assertSame(['other_sender' => $otherSender], iterator_to_array($locator->getSenders(new Envelope(new DummyMessageWithAttribute('a'))))); + $this->assertSame([], iterator_to_array($locator->getSenders(new Envelope(new SecondMessage())))); + } + public function testSendersMapWithFallback() { $firstSender = $this->createMock(SenderInterface::class); diff --git a/src/Symfony/Component/Messenger/Transport/Sender/SendersLocator.php b/src/Symfony/Component/Messenger/Transport/Sender/SendersLocator.php index 22850d8b089a6..9f711d91e5a96 100644 --- a/src/Symfony/Component/Messenger/Transport/Sender/SendersLocator.php +++ b/src/Symfony/Component/Messenger/Transport/Sender/SendersLocator.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Messenger\Transport\Sender; use Psr\Container\ContainerInterface; +use Symfony\Component\Messenger\Attribute\AsMessage; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Exception\RuntimeException; use Symfony\Component\Messenger\Handler\HandlersLocator; @@ -45,6 +46,7 @@ public function getSenders(Envelope $envelope): iterable } $seen = []; + $found = false; foreach (HandlersLocator::listTypes($envelope) as $type) { if (str_ends_with($type, '*') && $seen) { @@ -58,9 +60,37 @@ public function getSenders(Envelope $envelope): iterable $seen[] = $senderAlias; yield from $this->getSenderFromAlias($senderAlias); + $found = true; } } } + + // Let the configuration-driven map upper override message attributes, + // this allows environment-specific configuration overriding hardcoded + // transport name. + if ($found) { + return; + } + + foreach ($this->getTransportNamesFromAttribute($envelope) as $senderAlias) { + yield from $this->getSenderFromAlias($senderAlias); + } + } + + private function getTransportNamesFromAttribute(Envelope $envelope): array + { + $transports = []; + $message = $envelope->getMessage(); + + foreach ((new \ReflectionClass($message))->getAttributes(AsMessage::class, \ReflectionAttribute::IS_INSTANCEOF) as $refAttr) { + $asMessage = $refAttr->newInstance(); + + if ($asMessage->transport) { + $transports = \array_merge($transports, (array) $asMessage->transport); + } + } + + return $transports; } private function getSenderFromAlias(string $senderAlias): iterable