Skip to content

Commit 574c4ea

Browse files
committed
[Messenger] Deprecate HandleTrait in favor of a new SingleHandlingTrait
1 parent c4e97eb commit 574c4ea

File tree

6 files changed

+206
-1
lines changed

6 files changed

+206
-1
lines changed

UPGRADE-7.1.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
UPGRADE FROM 7.0 to 7.1
22
=======================
33

4+
Messenger
5+
---------
6+
7+
* Deprecate `HandleTrait`, use `SingleHandlingTrait` instead
8+
49
Workflow
510
--------
611

src/Symfony/Component/Messenger/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ CHANGELOG
66

77
* Add option `redis_sentinel` as an alias for `sentinel_master`
88
* Add `--all` option to the `messenger:consume` command
9+
* Deprecate `HandleTrait`, use `SingleHandlingTrait` instead
910

1011
7.0
1112
---

src/Symfony/Component/Messenger/HandleTrait.php

+4
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,14 @@
1414
use Symfony\Component\Messenger\Exception\LogicException;
1515
use Symfony\Component\Messenger\Stamp\HandledStamp;
1616

17+
trigger_deprecation('symfony/messenger', '7.1', 'The "%s" class is deprecated, use "%s" instead.', __CLASS__, SingleHandlingTrait::class);
18+
1719
/**
1820
* Leverages a message bus to expect a single, synchronous message handling and return its result.
1921
*
2022
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
23+
*
24+
* @deprecated since Symfony 7.1, use SingleHandlingTrait instead.
2125
*/
2226
trait HandleTrait
2327
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\HandlerFailedException;
15+
use Symfony\Component\Messenger\Exception\LogicException;
16+
use Symfony\Component\Messenger\Stamp\HandledStamp;
17+
18+
trait SingleHandlingTrait
19+
{
20+
private MessageBusInterface $messageBus;
21+
22+
/**
23+
* Dispatches the given message, expecting to be handled by a single handler
24+
* and returns the result from the handler returned value.
25+
* This behavior is useful for both synchronous command & query buses,
26+
* the last one usually returning the handler result.
27+
*
28+
* @param object|Envelope $message The message or the message pre-wrapped in an envelope
29+
*/
30+
private function handle(object $message): mixed
31+
{
32+
if (!isset($this->messageBus)) {
33+
throw new LogicException(sprintf('You must provide a "%s" instance in the "%s::$messageBus" property, but that property has not been initialized yet.', MessageBusInterface::class, static::class));
34+
}
35+
36+
$exceptions = [];
37+
38+
try {
39+
$envelope = $this->messageBus->dispatch($message);
40+
} catch (HandlerFailedException $exception) {
41+
$envelope = $exception->getEnvelope();
42+
$exceptions = $exception->getWrappedExceptions();
43+
}
44+
45+
/** @var HandledStamp[] $handledStamps */
46+
$handledStamps = $envelope->all(HandledStamp::class);
47+
48+
$handlers = array_merge(
49+
array_map(static fn (HandledStamp $stamp) => $stamp->getHandlerName(), $handledStamps),
50+
array_keys($exceptions),
51+
);
52+
53+
if (!$handlers) {
54+
throw new LogicException(sprintf('Message of type "%s" was handled zero times. Exactly one handler is expected when using "%s::%s()".', get_debug_type($envelope->getMessage()), static::class, __FUNCTION__));
55+
}
56+
57+
if (\count($handlers) > 1) {
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_debug_type($envelope->getMessage()), static::class, __FUNCTION__, \count($handlers), implode('", "', $handlers)));
59+
}
60+
61+
if ($exceptions) {
62+
throw reset($exceptions);
63+
}
64+
65+
return $handledStamps[0]->getResult();
66+
}
67+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Tests;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Messenger\Envelope;
16+
use Symfony\Component\Messenger\Exception\HandlerFailedException;
17+
use Symfony\Component\Messenger\Exception\LogicException;
18+
use Symfony\Component\Messenger\MessageBus;
19+
use Symfony\Component\Messenger\MessageBusInterface;
20+
use Symfony\Component\Messenger\SingleHandlingTrait;
21+
use Symfony\Component\Messenger\Stamp\HandledStamp;
22+
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
23+
24+
class SingleHandlingTraitTest extends TestCase
25+
{
26+
public function testItThrowsOnNoMessageBusInstance()
27+
{
28+
$this->expectException(LogicException::class);
29+
$this->expectExceptionMessage('You must provide a "Symfony\Component\Messenger\MessageBusInterface" instance in the "Symfony\Component\Messenger\Tests\TestQueryBus::$messageBus" property, but that property has not been initialized yet.');
30+
$queryBus = new TestQueryBus(null);
31+
$query = new DummyMessage('Hello');
32+
33+
$queryBus->query($query);
34+
}
35+
36+
public function testHandleReturnsHandledStampResult()
37+
{
38+
$bus = $this->createMock(MessageBus::class);
39+
$queryBus = new TestQueryBus($bus);
40+
41+
$query = new DummyMessage('Hello');
42+
$bus->expects($this->once())->method('dispatch')->willReturn(
43+
new Envelope($query, [new HandledStamp('result', 'DummyHandler::__invoke')])
44+
);
45+
46+
$this->assertSame('result', $queryBus->query($query));
47+
}
48+
49+
public function testHandleAcceptsEnvelopes()
50+
{
51+
$bus = $this->createMock(MessageBus::class);
52+
$queryBus = new TestQueryBus($bus);
53+
54+
$envelope = new Envelope(new DummyMessage('Hello'), [new HandledStamp('result', 'DummyHandler::__invoke')]);
55+
$bus->expects($this->once())->method('dispatch')->willReturn($envelope);
56+
57+
$this->assertSame('result', $queryBus->query($envelope));
58+
}
59+
60+
public function testHandleThrowsOnNoHandledStamp()
61+
{
62+
$this->expectException(LogicException::class);
63+
$this->expectExceptionMessage('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()".');
64+
$bus = $this->createMock(MessageBus::class);
65+
$queryBus = new TestQueryBus($bus);
66+
67+
$query = new DummyMessage('Hello');
68+
$bus->expects($this->once())->method('dispatch')->willReturn(new Envelope($query));
69+
70+
$queryBus->query($query);
71+
}
72+
73+
public function testHandleThrowsOnMultipleHandledStamps()
74+
{
75+
$this->expectException(LogicException::class);
76+
$this->expectExceptionMessage('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", "SecondDummyHandler::__invoke".');
77+
$bus = $this->createMock(MessageBus::class);
78+
$queryBus = new TestQueryBus($bus);
79+
80+
$query = new DummyMessage('Hello');
81+
$bus->expects($this->once())->method('dispatch')->willThrowException(
82+
new HandlerFailedException(
83+
new Envelope($query, [new HandledStamp('first_result', 'FirstDummyHandler::__invoke')]),
84+
['SecondDummyHandler::__invoke' => new \RuntimeException('SecondDummyHandler failed.')]
85+
)
86+
);
87+
88+
$queryBus->query($query);
89+
}
90+
91+
public function testHandleThrowsWrappedException()
92+
{
93+
$bus = $this->createMock(MessageBus::class);
94+
$queryBus = new TestQueryBus($bus);
95+
96+
$query = new DummyMessage('Hello');
97+
$wrappedException = new \RuntimeException('Handler failed.');
98+
$bus->expects($this->once())->method('dispatch')->willThrowException(
99+
new HandlerFailedException(
100+
new Envelope($query),
101+
['DummyHandler::__invoke' => new \RuntimeException('Handler failed.')]
102+
)
103+
);
104+
105+
$this->expectException($wrappedException::class);
106+
$this->expectExceptionMessage($wrappedException->getMessage());
107+
108+
$queryBus->query($query);
109+
}
110+
}
111+
112+
class TestQueryBus
113+
{
114+
use SingleHandlingTrait;
115+
116+
public function __construct(?MessageBusInterface $messageBus)
117+
{
118+
if ($messageBus) {
119+
$this->messageBus = $messageBus;
120+
}
121+
}
122+
123+
public function query($query): string
124+
{
125+
return $this->handle($query);
126+
}
127+
}

src/Symfony/Component/Messenger/composer.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
"require": {
1919
"php": ">=8.2",
2020
"psr/log": "^1|^2|^3",
21-
"symfony/clock": "^6.4|^7.0"
21+
"symfony/clock": "^6.4|^7.0",
22+
"symfony/deprecation-contracts": "^2.5|^3"
2223
},
2324
"require-dev": {
2425
"psr/cache": "^1.0|^2.0|^3.0",

0 commit comments

Comments
 (0)