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

Skip to content

Commit 5a1bcec

Browse files
committed
Ensure message is handled only once per handler
Add check to ensure that a message is only handled once per handler Add try...catch to run all handlers before throwing exception
1 parent a3b45eb commit 5a1bcec

File tree

4 files changed

+111
-13
lines changed

4 files changed

+111
-13
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Symfony\Component\Messenger\Exception;
4+
5+
use Symfony\Component\Messenger\Envelope;
6+
7+
class ChainedHandlerFailedException extends \RuntimeException implements ExceptionInterface
8+
{
9+
/**
10+
* @var \Throwable[]
11+
*/
12+
private $nested;
13+
14+
/**
15+
* @var Envelope
16+
*/
17+
private $envelope;
18+
19+
public function __construct(Envelope $envelope, \Throwable ...$nested)
20+
{
21+
$this->envelope = $envelope;
22+
$this->nested = $nested;
23+
parent::__construct();
24+
}
25+
26+
public function getEnvelope(): Envelope
27+
{
28+
return $this->envelope;
29+
}
30+
31+
/**
32+
* @return \Throwable[]
33+
*/
34+
public function getNestedExceptions(): array
35+
{
36+
return $this->nested;
37+
}
38+
}

src/Symfony/Component/Messenger/Middleware/HandleMessageMiddleware.php

100644100755
Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\Messenger\Middleware;
1313

1414
use Symfony\Component\Messenger\Envelope;
15+
use Symfony\Component\Messenger\Exception\ChainedHandlerFailedException;
1516
use Symfony\Component\Messenger\Exception\NoHandlerForMessageException;
1617
use Symfony\Component\Messenger\Handler\HandlersLocatorInterface;
1718
use Symfony\Component\Messenger\Stamp\HandledStamp;
@@ -41,13 +42,39 @@ public function handle(Envelope $envelope, StackInterface $stack): Envelope
4142
{
4243
$handler = null;
4344
$message = $envelope->getMessage();
45+
$exceptions = [];
4446
foreach ($this->handlersLocator->getHandlers($envelope) as $alias => $handler) {
45-
$envelope = $envelope->with(HandledStamp::fromCallable($handler, $handler($message), \is_string($alias) ? $alias : null));
47+
$alias = \is_string($alias) ? $alias : null;
48+
49+
if ($this->hasMessageSeen($envelope, $handler, $alias)) {
50+
continue;
51+
}
52+
53+
try {
54+
$envelope = $envelope->with(HandledStamp::fromCallable($handler, $handler($message), $alias));
55+
} catch (\Throwable $e) {
56+
$exceptions[] = $e;
57+
}
4658
}
4759
if (null === $handler && !$this->allowNoHandlers) {
4860
throw new NoHandlerForMessageException(sprintf('No handler for message "%s".', \get_class($envelope->getMessage())));
4961
}
5062

63+
if (\count($exceptions)) {
64+
throw new ChainedHandlerFailedException($envelope, ...$exceptions);
65+
}
66+
5167
return $stack->next()->handle($envelope, $stack);
5268
}
69+
70+
private function hasMessageSeen(Envelope $envelope, callable $handler, ?string $alias): bool
71+
{
72+
$some = array_filter($envelope
73+
->all(HandledStamp::class), function (HandledStamp $stamp) use ($handler, $alias){
74+
return $stamp->getCallableName() === HandledStamp::getNameFromCallable($handler) &&
75+
$stamp->getHandlerAlias() === $alias;
76+
});
77+
78+
return \count($some) > 0;
79+
}
5380
}

src/Symfony/Component/Messenger/Stamp/HandledStamp.php

100644100755
Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,32 +41,37 @@ public function __construct($result, string $callableName, string $handlerAlias
4141
* @param mixed $result The returned value of the message handler
4242
*/
4343
public static function fromCallable(callable $handler, $result, string $handlerAlias = null): self
44+
{
45+
return new self($result, self::getNameFromCallable($handler), $handlerAlias);
46+
}
47+
48+
public static function getNameFromCallable(callable $handler): string
4449
{
4550
if (\is_array($handler)) {
4651
if (\is_object($handler[0])) {
47-
return new self($result, \get_class($handler[0]).'::'.$handler[1], $handlerAlias);
52+
return \get_class($handler[0]).'::'.$handler[1];
4853
}
4954

50-
return new self($result, $handler[0].'::'.$handler[1], $handlerAlias);
55+
return $handler[0].'::'.$handler[1];
5156
}
5257

5358
if (\is_string($handler)) {
54-
return new self($result, $handler, $handlerAlias);
59+
return $handler;
5560
}
5661

5762
if ($handler instanceof \Closure) {
5863
$r = new \ReflectionFunction($handler);
5964
if (false !== strpos($r->name, '{closure}')) {
60-
return new self($result, 'Closure', $handlerAlias);
65+
return 'Closure';
6166
}
6267
if ($class = $r->getClosureScopeClass()) {
63-
return new self($result, $class->name.'::'.$r->name, $handlerAlias);
68+
return $class->name.'::'.$r->name;
6469
}
6570

66-
return new self($result, $r->name, $handlerAlias);
71+
return $r->name;
6772
}
6873

69-
return new self($result, \get_class($handler).'::__invoke', $handlerAlias);
74+
return \get_class($handler).'::__invoke';
7075
}
7176

7277
/**

src/Symfony/Component/Messenger/Tests/Middleware/HandleMessageMiddlewareTest.php

100644100755
Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\Messenger\Tests\Middleware;
1313

1414
use Symfony\Component\Messenger\Envelope;
15+
use Symfony\Component\Messenger\Exception\ChainedHandlerFailedException;
1516
use Symfony\Component\Messenger\Handler\HandlersLocator;
1617
use Symfony\Component\Messenger\Middleware\HandleMessageMiddleware;
1718
use Symfony\Component\Messenger\Middleware\StackMiddleware;
@@ -40,7 +41,7 @@ public function testItCallsTheHandlerAndNextMiddleware()
4041
/**
4142
* @dataProvider itAddsHandledStampsProvider
4243
*/
43-
public function testItAddsHandledStamps(array $handlers, array $expectedStamps)
44+
public function testItAddsHandledStamps(array $handlers, array $expectedStamps, $nextIsCalled)
4445
{
4546
$message = new DummyMessage('Hey');
4647
$envelope = new Envelope($message);
@@ -49,7 +50,11 @@ public function testItAddsHandledStamps(array $handlers, array $expectedStamps)
4950
DummyMessage::class => $handlers,
5051
]));
5152

52-
$envelope = $middleware->handle($envelope, $this->getStackMock());
53+
try {
54+
$envelope = $middleware->handle($envelope, $this->getStackMock($nextIsCalled));
55+
} catch (ChainedHandlerFailedException $e) {
56+
$envelope = $e->getEnvelope();
57+
}
5358

5459
$this->assertEquals($expectedStamps, $envelope->all(HandledStamp::class));
5560
}
@@ -64,17 +69,22 @@ public function itAddsHandledStampsProvider()
6469
$second->method('__invoke')->willReturn(null);
6570
$secondClass = \get_class($second);
6671

72+
$failing = $this->createPartialMock(\stdClass::class, ['__invoke']);
73+
$failing->method('__invoke')->will($this->throwException(new \Exception('handler failed.')));
74+
6775
yield 'A stamp is added' => [
6876
[$first],
6977
[new HandledStamp('first result', $firstClass.'::__invoke')],
78+
true,
7079
];
7180

7281
yield 'A stamp is added per handler' => [
73-
[$first, $second],
82+
['first' => $first, 'second' => $second],
7483
[
75-
new HandledStamp('first result', $firstClass.'::__invoke'),
76-
new HandledStamp(null, $secondClass.'::__invoke'),
84+
new HandledStamp('first result', $firstClass.'::__invoke', 'first'),
85+
new HandledStamp(null, $secondClass.'::__invoke', 'second'),
7786
],
87+
true,
7888
];
7989

8090
yield 'Yielded locator alias is used' => [
@@ -83,6 +93,24 @@ public function itAddsHandledStampsProvider()
8393
new HandledStamp('first result', $firstClass.'::__invoke', 'first_alias'),
8494
new HandledStamp(null, $secondClass.'::__invoke'),
8595
],
96+
true,
97+
];
98+
99+
yield 'It tries all handlers' => [
100+
['first' => $first, 'failing' => $failing, 'second' => $second],
101+
[
102+
new HandledStamp('first result', $firstClass.'::__invoke', 'first'),
103+
new HandledStamp(null, $secondClass.'::__invoke', 'second')
104+
],
105+
false,
106+
];
107+
108+
yield 'It ignores duplicated handler' => [
109+
[$first, $first],
110+
[
111+
new HandledStamp('first result', $firstClass.'::__invoke'),
112+
],
113+
true,
86114
];
87115
}
88116

0 commit comments

Comments
 (0)