Skip to content

Commit d652842

Browse files
committed
feature symfony#57506 [Messenger] introduce AsMessage attribute for message routing
1 parent 2cb470e commit d652842

File tree

5 files changed

+131
-0
lines changed

5 files changed

+131
-0
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Messenger\Attribute;
13+
14+
/**
15+
* Attribute for configuring message routing.
16+
*
17+
* @author Pierre Rineau [email protected]>
18+
*/
19+
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
20+
class AsMessage
21+
{
22+
public function __construct(
23+
/**
24+
* Name of the transports to which the message should be routed.
25+
*/
26+
public null|string|array $transport = null,
27+
) {
28+
}
29+
}

src/Symfony/Component/Messenger/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
7.2
55
---
66

7+
* Add `#[AsMessage]` attribute with `$transport` parameter for message routing
78
* Add `--format` option to the `messenger:stats` command
89

910
7.1
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace Symfony\Component\Messenger\Tests\Fixtures;
4+
5+
use Symfony\Component\Messenger\Attribute\AsMessage;
6+
7+
#[AsMessage(transport: ['first_sender', 'second_sender'])]
8+
class DummyMessageWithAttribute implements DummyMessageInterface
9+
{
10+
public function __construct(
11+
private string $message,
12+
) {
13+
}
14+
15+
public function getMessage(): string
16+
{
17+
return $this->message;
18+
}
19+
}

src/Symfony/Component/Messenger/Tests/Transport/Sender/SendersLocatorTest.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
use Symfony\Component\Messenger\Envelope;
1818
use Symfony\Component\Messenger\Stamp\TransportNamesStamp;
1919
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
20+
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessageInterface;
21+
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessageWithAttribute;
2022
use Symfony\Component\Messenger\Tests\Fixtures\SecondMessage;
2123
use Symfony\Component\Messenger\Transport\Sender\SenderInterface;
2224
use Symfony\Component\Messenger\Transport\Sender\SendersLocator;
@@ -53,6 +55,56 @@ public function testItReturnsTheSenderBasedOnTransportNamesStamp()
5355
$this->assertSame([], iterator_to_array($locator->getSenders(new Envelope(new SecondMessage()))));
5456
}
5557

58+
public function testItReturnsTheSenderBasedOnAsMessageAttribute()
59+
{
60+
$firstSender = $this->createMock(SenderInterface::class);
61+
$secondSender = $this->createMock(SenderInterface::class);
62+
$otherSender = $this->createMock(SenderInterface::class);
63+
$sendersLocator = $this->createContainer([
64+
'first_sender' => $firstSender,
65+
'second_sender' => $secondSender,
66+
'other_sender' => $otherSender,
67+
]);
68+
$locator = new SendersLocator([], $sendersLocator);
69+
70+
$this->assertSame(['first_sender' => $firstSender, 'second_sender' => $secondSender], iterator_to_array($locator->getSenders(new Envelope(new DummyMessageWithAttribute('a')))));
71+
$this->assertSame([], iterator_to_array($locator->getSenders(new Envelope(new SecondMessage()))));
72+
}
73+
74+
public function testAsMessageAttributeIsOverridenByTransportNamesStamp()
75+
{
76+
$firstSender = $this->createMock(SenderInterface::class);
77+
$secondSender = $this->createMock(SenderInterface::class);
78+
$otherSender = $this->createMock(SenderInterface::class);
79+
$sendersLocator = $this->createContainer([
80+
'first_sender' => $firstSender,
81+
'second_sender' => $secondSender,
82+
'other_sender' => $otherSender,
83+
]);
84+
$locator = new SendersLocator([], $sendersLocator);
85+
86+
$this->assertSame(['other_sender' => $otherSender], iterator_to_array($locator->getSenders(new Envelope(new DummyMessageWithAttribute('a'), [new TransportNamesStamp(['other_sender'])]))));
87+
$this->assertSame([], iterator_to_array($locator->getSenders(new Envelope(new SecondMessage()))));
88+
}
89+
90+
public function testAsMessageAttributeIsOverridenByUserConfiguration()
91+
{
92+
$firstSender = $this->createMock(SenderInterface::class);
93+
$secondSender = $this->createMock(SenderInterface::class);
94+
$otherSender = $this->createMock(SenderInterface::class);
95+
$sendersLocator = $this->createContainer([
96+
'first_sender' => $firstSender,
97+
'second_sender' => $secondSender,
98+
'other_sender' => $otherSender,
99+
]);
100+
$locator = new SendersLocator([
101+
DummyMessageInterface::class => ['other_sender'],
102+
], $sendersLocator);
103+
104+
$this->assertSame(['other_sender' => $otherSender], iterator_to_array($locator->getSenders(new Envelope(new DummyMessageWithAttribute('a')))));
105+
$this->assertSame([], iterator_to_array($locator->getSenders(new Envelope(new SecondMessage()))));
106+
}
107+
56108
public function testSendersMapWithFallback()
57109
{
58110
$firstSender = $this->createMock(SenderInterface::class);

src/Symfony/Component/Messenger/Transport/Sender/SendersLocator.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\Messenger\Transport\Sender;
1313

1414
use Psr\Container\ContainerInterface;
15+
use Symfony\Component\Messenger\Attribute\AsMessage;
1516
use Symfony\Component\Messenger\Envelope;
1617
use Symfony\Component\Messenger\Exception\RuntimeException;
1718
use Symfony\Component\Messenger\Handler\HandlersLocator;
@@ -45,6 +46,7 @@ public function getSenders(Envelope $envelope): iterable
4546
}
4647

4748
$seen = [];
49+
$found = false;
4850

4951
foreach (HandlersLocator::listTypes($envelope) as $type) {
5052
if (str_ends_with($type, '*') && $seen) {
@@ -58,9 +60,37 @@ public function getSenders(Envelope $envelope): iterable
5860
$seen[] = $senderAlias;
5961

6062
yield from $this->getSenderFromAlias($senderAlias);
63+
$found = true;
6164
}
6265
}
6366
}
67+
68+
// Let the configuration-driven map upper override message attributes,
69+
// this allows environment-specific configuration overriding hardcoded
70+
// transport name.
71+
if ($found) {
72+
return;
73+
}
74+
75+
foreach ($this->getTransportNamesFromAttribute($envelope) as $senderAlias) {
76+
yield from $this->getSenderFromAlias($senderAlias);
77+
}
78+
}
79+
80+
private function getTransportNamesFromAttribute(Envelope $envelope): array
81+
{
82+
$transports = [];
83+
$message = $envelope->getMessage();
84+
85+
foreach ((new \ReflectionClass($message))->getAttributes(AsMessage::class, \ReflectionAttribute::IS_INSTANCEOF) as $refAttr) {
86+
$asMessage = $refAttr->newInstance();
87+
88+
if ($asMessage->transport) {
89+
$transports = \array_merge($transports, (array) $asMessage->transport);
90+
}
91+
}
92+
93+
return $transports;
6494
}
6595

6696
private function getSenderFromAlias(string $senderAlias): iterable

0 commit comments

Comments
 (0)