8000 [Messenger] Deprecate `HandleTrait` in favor of a new `SingleHandling… · symfony/symfony@da5c907 · GitHub
[go: up one dir, main page]

Skip to content

Commit da5c907

Browse files
committed
[Messenger] Deprecate HandleTrait in favor of a new SingleHandlingTrait
1 parent 7f3d8c6 commit da5c907

10 files changed

+278
-2
lines changed

UPGRADE-7.1.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Messenger
1010
---------
1111

1212
* Make `#[AsMessageHandler]` final
13+
* Deprecate `HandleTrait`, use `SingleHandlingTrait` instead
1314

1415
Workflow
1516
--------

src/Symfony/Component/Messenger/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ CHANGELOG
77
* Add option `redis_sentinel` as an alias for `sentinel_master`
88
* Add `--all` option to the `messenger:consume` command
99
* Make `#[AsMessageHandler]` final
10+
* Deprecate `HandleTrait`, use `SingleHandlingTrait` instead
1011

1112
7.0
1213
---

src/Symfony/Component/Messenger/HandleTrait.php

Lines changed: 4 additions & 0 deletions
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.', HandleTrait::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
{
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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+
use Symfony\Component\Messenger\Stamp\StampInterface;
18+
19+
trait SingleHandlingTrait
20+
{
21+
private readonly MessageBusInterface $messageBus;
22+
23+
/**
24+
* Dispatches the given message, expecting to be handled by a single handler
25+
* and returns the result from the handler returned value.
26+
* This behavior is useful for both synchronous command & query buses,
27+
* the last one usually returning the handler result.
28+
*
29+
* @param object|Envelope $message The message or the message pre-wrapped in an envelope
30+
* @param StampInterface[] $stamps
31+
*/
32+
private function handle(object $message, array $stamps = []): mixed
33+
{
34+
if (!isset($this->messageBus)) {
35+
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));
36+
}
37+
38+
$exceptions = [];
39+
40+
try {
41+
$envelope = $this->messageBus->dispatch($message, $stamps);
42+
} catch (HandlerFailedException $exception) {
43+
$envelope = $exception->getEnvelope();
44+
$exceptions = $exception->getWrappedExceptions();
45+
}
46+
47+
/** @var HandledStamp[] $handledStamps */
48+
$handledStamps = $envelope->all(HandledStamp::class);
49+
50+
$handlers = array_merge(
51+
array_map(static fn (HandledStamp $stamp) => $stamp->getHandlerName(), $handledStamps),
52+
array_keys($exceptions),
53+
);
54+
55+
if (!$handlers) {
56+
throw new LogicException(sprintf('Message of type "%s" was handled zero times. Exactly one handler is expected when using "%s::%s()".', $envelope->getMessage()::class, static::class, __FUNCTION__));
57+
}
58+
59+
if (\count($handlers) > 1) {
60+
throw new LogicException(sprintf('Message of type "%s" was handled multiple times. Only one handler is expected when using "%s::%s()", got %d: "%s".', $envelope->getMessage()::class, static::class, __FUNCTION__, \count($handlers), implode('", "', $handlers)));
61+
}
62+
63+
if ($exceptions) {
64+
throw reset($exceptions);
65+
}
66+
67+
return $handledStamps[0]->getResult();
68+
}
69+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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\Fixtures;
13+
14+
use Symfony\Component\Messenger\MessageBusInterface;
15+
use Symfony\Component\Messenger\SingleHandlingTrait;
16+
17+
/**
18+
* @see \Symfony\Component\Messenger\Tests\TraceableMessageBusTest::testItTracesDispatchWhenSingleHandlingTraitIsUsed
19+
*/
20+
class TestTracesWithSingleHandlingTraitAction
21+
{
22+
use SingleHandlingTrait;
23+
24+
public function __construct(MessageBusInterface $messageBus)
25+
{
26+
$this->messageBus = $messageBus;
27+
}
28+
29+
public function __invoke($message)
30+
{
31+
$this->handle($message);
32+
}
33+
}

src/Symfony/Component/Messenger/Tests/HandleTraitTest.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
use Symfony\Component\Messenger\Stamp\HandledStamp;
2121
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
2222

23+
/**
24+
* @group legacy
25+
*/
2326
class HandleTraitTest extends TestCase
2427
{
2528
public function testItThrowsOnNoMessageBusInstance()
Lines changed: 127 additions & 0 deletions
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\SingleHandlerBus::$messageBus" property, but that property has not been initialized yet.');
30+
$singleHandlerBus = new SingleHandlerBus(null);
31+
$message = new DummyMessage('Hello');
32+
33+
$singleHandlerBus->dispatch($message);
34+
}
35+
36+
public function testHandleReturnsHandledStampResult()
37+
{
38+
$bus = $this->createMock(MessageBus::class);
39+
$singleHandlerBus = new SingleHandlerBus($bus);
40+
41+
$message = new DummyMessage('Hello');
42+
$bus->expects($this->once())->method('dispatch')->willReturn(
43+
new Envelope($message, [new HandledStamp('result', 'DummyHandler::__invoke')])
44+
);
45+
46+
$this->assertSame('result', $singleHandlerBus->dispatch($message));
47+
}
48+
49+
public function testHandleAcceptsEnvelopes()
50+
{
51+
$bus = $this->createMock(MessageBus::class);
52+
$singleHandlerBus = new SingleHandlerBus($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', $singleHandlerBus->dispatch($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\SingleHandlerBus::handle()".');
64+
$bus = $this->createMock(MessageBus::class);
65+
$singleHandlerBus = new SingleHandlerBus($bus);
66+
67+
$message = new DummyMessage('Hello');
68+
$bus->expects($this->once())->method('dispatch')->willReturn(new Envelope($message));
69+
70+
$singleHandlerBus->dispatch($message);
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\SingleHandlerBus::handle()", got 2: "FirstDummyHandler::__invoke", "SecondDummyHandler::__invoke".');
77+
$bus = $this->createMock(MessageBus::class);
78+
$singleHandlerBus = new SingleHandlerBus($bus);
79+
80+
$message = new DummyMessage('Hello');
81+
$bus->expects($this->once())->method('dispatch')->willThrowException(
82+
new HandlerFailedException(
83+
new Envelope($message, [new HandledStamp('first_result', 'FirstDummyHandler::__invoke')]),
84+
['SecondDummyHandler::__invoke' => new \RuntimeException('SecondDummyHandler failed.')]
85+
)
86+
);
87+
88+
$singleHandlerBus->dispatch($message);
89+
}
90+
91+
public function testHandleThrowsWrappedException()
92+
{
93+
$bus = $this->createMock(MessageBus::class);
94+
$singleHandlerBus = new SingleHandlerBus($bus);
95+
96+
$message = new DummyMessage('Hello');
97+
$wrappedException = new \RuntimeException('Handler failed.');
98+
$bus->expects($this->once())->method('dispatch')->willThrowException(
99+
new HandlerFailedException(
100+
new Envelope($message),
101+
['DummyHandler::__invoke' => new \RuntimeException('Handler failed.')]
102+
)
103+
);
104+
105+
$this->expectException($wrappedException::class);
106+
$this->expectExceptionMessage($wrappedException->getMessage());
107+
108+
$singleHandlerBus->dispatch($message);
109+
}
110+
}
111+
112+
class SingleHandlerBus
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 dispatch($message): string
124+
{
125+
return $this->handle($message);
126+
}
127+
}

src/Symfony/Component/Messenger/Tests/TraceableMessageBusTest.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,28 @@
1212
namespace Symfony\Component\Messenger\Tests;
1313

1414
use PHPUnit\Framework\TestCase;
15+
use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait;
1516
use Symfony\Component\Messenger\Envelope;
1617
use Symfony\Component\Messenger\MessageBusInterface;
1718
use Symfony\Component\Messenger\Stamp\DelayStamp;
1819
use Symfony\Component\Messenger\Stamp\HandledStamp;
1920
use Symfony\Component\Messenger\Tests\Fixtures\AnEnvelopeStamp;
2021
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
2122
use Symfony\Component\Messenger\Tests\Fixtures\TestTracesWithHandleTraitAction;
23+
use Symfony\Component\Messenger\Tests\Fixtures\TestTracesWithSingleHandlingTraitAction;
2224
use Symfony\Component\Messenger\TraceableMessageBus;
2325

2426
class TraceableMessageBusTest extends TestCase
2527
{
28+
use ExpectDeprecationTrait;
29+
30+
/**
31+
* @group legacy
32+
*/
2633
public function testItTracesDispatch()
2734
{
35+
$this->expectDeprecation('Since symfony/messenger 7.1: The "Symfony\Component\Messenger\HandleTrait" class is deprecated, use "Symfony\Component\Messenger\SingleHandlingTrait" instead.');
36+
2837
$message = new DummyMessage('Hello');
2938

3039
$stamp = new DelayStamp(5);
@@ -49,6 +58,9 @@ public function testItTracesDispatch()
4958
], $actualTracedMessage);
5059
}
5160

61+
/**
62+
* @group legacy
63+
*/
5264
public function testItTracesDispatchWhenHandleTraitIsUsed()
5365
{
5466
$message = new DummyMessage('Hello');
@@ -73,6 +85,30 @@ public function testItTracesDispatchWhenHandleTraitIsUsed()
7385
], $actualTracedMessage);
7486
}
7587

88+
public function testItTracesDispatchWhenSingleHandlingTraitIsUsed()
89+
{
90+
$message = new DummyMessage('Hello');
91+
92+
$bus = $this->createMock(MessageBusInterface::class);
93+
$bus->expects($this->once())->method('dispatch')->with($message)->willReturn((new Envelope($message))->with($stamp = new HandledStamp('result', 'handlerName')));
94+
95+
$traceableBus = new TraceableMessageBus($bus);
96+
(new TestTracesWithSingleHandlingTraitAction($traceableBus))($message);
97+
$this->assertCount(1, $tracedMessages = $traceableBus->getDispatchedMessages());
98+
$actualTracedMessage = $tracedMessages[0];
99+
unset($actualTracedMessage['callTime']); // don't check, too variable
100+
$this->assertEquals([
101+
'message' => $message,
102+
'stamps' => [],
103+
'stamps_after_dispatch' => [$stamp],
104+
'caller' => [
105+
'name' => 'TestTracesWithSingleHandlingTraitAction.php',
106+
'file' => (new \ReflectionClass(TestTracesWithSingleHandlingTraitAction::class))->getFileName(),
107+
'line' => (new \ReflectionMethod(TestTracesWithSingleHandlingTraitAction::class, '__invoke'))->getStartLine() + 2,
108+
],
109+
], $actualTracedMessage);
110+
}
111+
76112
public function testItTracesDispatchWithEnvelope()
77113
{
78114
$message = new DummyMessage('Hello');

src/Symfony/Component/Messenger/TraceableMessageBus.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,10 @@ private function getCaller(): array
6363
$line = $trace[1]['line'] ?? null;
6464

6565
$handleTraitFile = (new \ReflectionClass(HandleTrait::class))->getFileName();
66+
$singleHandlingTraitFile = (new \ReflectionClass(SingleHandlingTrait::class))->getFileName();
6667
$found = false;
6768
for ($i = 1; $i < 8; ++$i) {
68-
if (isset($trace[$i]['file'], $trace[$i + 1]['file'], $trace[$i + 1]['line']) && $trace[$i]['file'] === $handleTraitFile) {
69+
if (isset($trace[$i]['file'], $trace[$i + 1]['file'], $trace[$i + 1]['line']) && ($trace[$i]['file'] === $singleHandlingTraitFile || $trace[$i]['file'] === $handleTraitFile)) {
6970
$file = $trace[$i + 1]['file'];
7071
$line = $trace[$i + 1]['line'];
7172
$found = true;

src/Symfony/Component/Messenger/composer.json

Lines changed: 2 additions & 1 deletion
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)
0