Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 227cf2c

Browse files
feature #29167 [Messenger] Add a trait for synchronous query & command buses (ogizanagi)
This PR was merged into the 4.2-dev branch. Discussion ---------- [Messenger] Add a trait for synchronous query & command buses | Q | A | ------------- | --- | Branch? | 4.2 <!-- see below --> | Bug fix? | no | New feature? | yes <!-- don't forget to update src/**/CHANGELOG.md files --> | BC breaks? | no <!-- see https://symfony.com/bc --> | Deprecations? | no <!-- don't forget to update UPGRADE-*.md and src/**/CHANGELOG.md files --> | Tests pass? | yes <!-- please add some, will be required by reviewers --> | Fixed tickets | N/A <!-- #-prefixed issue number(s), if any --> | License | MIT | Doc PR | symfony/symfony-docs/issues/10662 Commits ------- 6ba4e8a [Messenger] Add a trait for synchronous query & command buses
2 parents ce6d918 + 6ba4e8a commit 227cf2c

File tree

3 files changed

+162
-0
lines changed

3 files changed

+162
-0
lines changed

src/Symfony/Component/Messenger/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ CHANGELOG
44
4.2.0
55
-----
66

7+
* Added `HandleTrait` leveraging a message bus instance to return a single
8+
synchronous message handling result
79
* Added `HandledStamp` & `SentStamp` stamps
810
* All the changes below are BC BREAKS
911
* Senders and handlers subscribing to parent interfaces now receive *all* matching messages, wildcard included
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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;
13+
14+
use Symfony\Component\Messenger\Exception\LogicException;
15+
use Symfony\Component\Messenger\Stamp\HandledStamp;
16+
17+
/**
18+
* Leverages a message bus to expect a single, synchronous message handling and return its result.
19+
*
20+
* @author Maxime Steinhausser <[email protected]>
21+
*
22+
* @experimental in 4.2
23+
*/
24+
trait HandleTrait
25+
{
26+
/** @var MessageBusInterface */
27+
private $messageBus;
28+
29+
/**
30+
* Dispatches the given message, expecting to be handled by a single handler
31+
* and returns the result from the handler returned value.
32+
* This behavior is useful for both synchronous command & query buses,
33+
* the last one usually returning the handler result.
34+
*
35+
* @param object|Envelope $message The message or the message pre-wrapped in an envelope
36+
*
37+
* @return mixed The handler returned value
38+
*/
39+
private function handle($message)
40+
{
41+
if (!$this->messageBus instanceof MessageBusInterface) {
42+
throw new LogicException(sprintf('You must provide a "%s" instance in the "%s::$messageBus" property, "%s" given.', MessageBusInterface::class, \get_class($this), \is_object($this->messageBus) ? \get_class($this->messageBus) : \gettype($this->messageBus)));
43+
}
44+
45+
$envelope = $this->messageBus->dispatch($message);
46+
/** @var HandledStamp[] $handledStamps */
47+
$handledStamps = $envelope->all(HandledStamp::class);
48+
49+
if (!$handledStamps) {
50+
throw new LogicException(sprintf('Message of type "%s" was handled zero times. Exactly one handler is expected when using "%s::%s()".', \get_class($envelope->getMessage()), \get_class($this), __FUNCTION__));
51+
}
52+
53+
if (\count($handledStamps) > 1) {
54+
$handlers = implode(', ', array_map(function (HandledStamp $stamp): string {
55+
return sprintf('"%s"', $stamp->getHandlerAlias() ?? $stamp->getCallableName());
56+
}, $handledStamps));
57+
58+
throw new LogicException(sprintf('Message of type "%s" was handled multiple times. Only one handler is expected when using "%s::%s()", got %d: %s.', \get_class($envelope->getMessage()), \get_class($this), __FUNCTION__, \count($handledStamps), $handlers));
59+
}
60+
61+
return $handledStamps[0]->getResult();
62+
}
63+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<?php
2+
3+
namespace Symfony\Component\Messenger\Tests;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use Symfony\Component\Messenger\Envelope;
7+
use Symfony\Component\Messenger\HandleTrait;
8+
use Symfony\Component\Messenger\MessageBus;
9+
use Symfony\Component\Messenger\MessageBusInterface;
10+
use Symfony\Component\Messenger\Stamp\HandledStamp;
11+
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
12+
13+
class HandleTraitTest extends TestCase
14+
{
15+
/**
16+
* @expectedException \Symfony\Component\Messenger\Exception\LogicException
17+
* @expectedExceptionMessage You must provide a "Symfony\Component\Messenger\MessageBusInterface" instance in the "Symfony\Component\Messenger\Tests\TestQueryBus::$messageBus" property, "NULL" given.
18+
*/
19+
public function testItThrowsOnNoMessageBusInstance()
20+
{
21+
$queryBus = new TestQueryBus(null);
22+
$query = new DummyMessage('Hello');
23+
24+
$queryBus->query($query);
25+
}
26+
27+
public function testHandleReturnsHandledStampResult()
28+
{
29+
$bus = $this->createMock(MessageBus::class);
30+
$queryBus = new TestQueryBus($bus);
31+
32+
$query = new DummyMessage('Hello');
33+
$bus->expects($this->once())->method('dispatch')->willReturn(
34+
new Envelope($query, new HandledStamp('result', 'DummyHandler::__invoke'))
35+
);
36+
37+
$this->assertSame('result', $queryBus->query($query));
38+
}
39+
40+
public function testHandleAcceptsEnvelopes()
41+
{
42+
$bus = $this->createMock(MessageBus::class);
43+
$queryBus = new TestQueryBus($bus);
44+
45+
$envelope = new Envelope(new DummyMessage('Hello'), new HandledStamp('result', 'DummyHandler::__invoke'));
46+
$bus->expects($this->once())->method('dispatch')->willReturn($envelope);
47+
48+
$this->assertSame('result', $queryBus->query($envelope));
49+
}
50+
51+
/**
52+
* @expectedException \Symfony\Component\Messenger\Exception\LogicException
53+
* @expectedExceptionMessage Message of type "Symfony\Component\Messenger\Tests\Fixtures\DummyMessage" was handled zero times. Exactly one handler is expected when using "Symfony\Component\Messenger\Tests\TestQueryBus::handle()".
54+
*/
55+
public function testHandleThrowsOnNoHandledStamp()
56+
{
57+
$bus = $this->createMock(MessageBus::class);
58+
$queryBus = new TestQueryBus($bus);
59+
60+
$query = new DummyMessage('Hello');
61+
$bus->expects($this->once())->method('dispatch')->willReturn(new Envelope($query));
62+
63+
$queryBus->query($query);
64+
}
65+
66+
/**
67+
* @expectedException \Symfony\Component\Messenger\Exception\LogicException
68+
* @expectedExceptionMessage Message of type "Symfony\Component\Messenger\Tests\Fixtures\DummyMessage" was handled multiple times. Only one handler is expected when using "Symfony\Component\Messenger\Tests\TestQueryBus::handle()", got 2: "FirstDummyHandler::__invoke", "dummy_2".
69+
*/
70+
public function testHandleThrowsOnMultipleHandledStamps()
71+
{
72+
$bus = $this->createMock(MessageBus::class);
73+
$queryBus = new TestQueryBus($bus);
74+
75+
$query = new DummyMessage('Hello');
76+
$bus->expects($this->once())->method('dispatch')->willReturn(
77+
new Envelope($query, new HandledStamp('first_result', 'FirstDummyHandler::__invoke'), new HandledStamp('second_result', 'SecondDummyHandler::__invoke', 'dummy_2'))
78+
);
79+
80+
$queryBus->query($query);
81+
}
82+
}
83+
84+
class TestQueryBus
85+
{
86+
use HandleTrait;
87+
88+
public function __construct(?MessageBusInterface $messageBus)
89+
{
90+
$this->messageBus = $messageBus;
91+
}
92+
93+
public function query($query): string
94+
{
95+
return $this->handle($query);
96+
}
97+
}

0 commit comments

Comments
 (0)