8000 feature #51653 [Messenger] Add WrappedExceptionsInterface for nested … · symfony/symfony@363e217 · GitHub
[go: up one dir, main page]

Skip to content

Commit 363e217

Browse files
committed
feature #51653 [Messenger] Add WrappedExceptionsInterface for nested exceptions (Jeroeny)
This PR was squashed before being merged into the 6.4 branch. Discussion ---------- [Messenger] Add WrappedExceptionsInterface for nested exceptions | Q | A | ------------- | --- | Branch? | 6.4 | Bug fix? | no | New feature? | yes | Deprecations? | yes | License | MIT Metric & logging tools often want to measure / log individual exceptions from the messenger. There are currently two exception classes that hold a bunch of collected exceptions from different messages or handlers. It would be nice if there was a single interface to check and call upon when extracting these nested exceptions. Example usecase: https://github.com/getsentry/sentry-symfony/pull/760/files#diff-da0fb4498178e4866e794b813999618022c327dea59f9277b86a7abf784aeafaR98 Commits ------- a3fe850 [Messenger] Add WrappedExceptionsInterface for nested exceptions
2 parents decb566 + a3fe850 commit 363e217

12 files changed

+148
-12
lines changed

UPGRADE-6.4.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ Messenger
113113
---------
114114

115115
* Deprecate `StopWorkerOnSignalsListener` in favor of using the `SignalableCommandInterface`
116+
* Deprecate `HandlerFailedException::getNestedExceptions()`, `HandlerFailedException::getNestedExceptionsOfClass()` and `DelayedMessageHandlingException::getExceptions()` which are replaced by a new `getWrappedExceptions()` method
116117

117118
MonologBridge
118119
-------------

src/Symfony/Bridge/Doctrine/Messenger/DoctrineTransactionMiddleware.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ protected function handleForManager(EntityManagerInterface $entityManager, Envel
3939
if ($exception instanceof HandlerFailedException) {
4040
// Remove all HandledStamp from the envelope so the retry will execute all handlers again.
4141
// When a handler fails, the queries of allegedly successful previous handlers just got rolled back.
42-
throw new HandlerFailedException($exception->getEnvelope()->withoutAll(HandledStamp::class), $exception->getNestedExceptions());
42+
throw new HandlerFailedException($exception->getEnvelope()->withoutAll(HandledStamp::class), $exception->getWrappedExceptions());
4343
}
4444

4545
throw $exception;

src/Symfony/Component/Mailer/Mailer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public function send(RawMessage $message, Envelope $envelope = null): void
6565
try {
6666
$this->bus->dispatch(new SendEmailMessage($message, $envelope), $stamps);
6767
} catch (HandlerFailedException $e) {
68-
foreach ($e->getNestedExceptions() as $nested) {
68+
foreach ($e->getWrappedExceptions() as $nested) {
6969
if ($nested instanceof TransportExceptionInterface) {
7070
throw $nested;
7171
}

src/Symfony/Component/Messenger/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ CHANGELOG
99
* Add support for multiple Redis Sentinel hosts
1010
* Add `--all` option to the `messenger:failed:remove` command
1111
* `RejectRedeliveredMessageException` implements `UnrecoverableExceptionInterface` in order to not be retried
12+
* Add `WrappedExceptionsInterface` interface for exceptions that hold multiple individual exceptions
13+
* Deprecate `HandlerFailedException::getNestedExceptions()`, `HandlerFailedException::getNestedExceptionsOfClass()`
14+
and `DelayedMessageHandlingException::getExceptions()` which are replaced by a new `getWrappedExceptions()` method
1215

1316
6.3
1417
---

src/Symfony/Component/Messenger/EventListener/SendFailedMessageForRetryListener.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ private function shouldRetry(\Throwable $e, Envelope $envelope, RetryStrategyInt
128128
// if ALL nested Exceptions are an instance of UnrecoverableExceptionInterface we should not retry
129129
if ($e instanceof HandlerFailedException) {
130130
$shouldNotRetry = true;
131-
foreach ($e->getNestedExceptions() as $nestedException) {
131+
foreach ($e->getWrappedExceptions() as $nestedException) {
132132
if ($nestedExcepti F438 on instanceof RecoverableExceptionInterface) {
133133
return true;
134134
}

src/Symfony/Component/Messenger/EventListener/StopWorkerOnCustomStopExceptionListener.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public function onMessageFailed(WorkerMessageFailedEvent $event): void
3131
$this->stop = true;
3232
}
3333
if ($th instanceof HandlerFailedException) {
34-
foreach ($th->getNestedExceptions() as $e) {
34+
foreach ($th->getWrappedExceptions() as $e) {
3535
if ($e instanceof StopWorkerExceptionInterface) {
3636
$this->stop = true;
3737
break;

src/Symfony/Component/Messenger/Exception/DelayedMessageHandlingException.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@
1919
*
2020
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
2121
*/
22-
class DelayedMessageHandlingException extends RuntimeException
22+
class DelayedMessageHandlingException extends RuntimeException implements WrappedExceptionsInterface
2323
{
24+
use WrappedExceptionsTrait;
25+
2426
private array $exceptions;
2527
private Envelope $envelope;
2628

@@ -41,11 +43,16 @@ public function __construct(array $exceptions, Envelope $envelope)
4143

4244
$this->exceptions = $exceptions;
4345

44-
parent::__co 10000 nstruct($message, 0, $exceptions[0]);
46+
parent::__construct($message, 0, $exceptions[array_key_first($exceptions)]);
4547
}
4648

49+
/**
50+
* @deprecated since Symfony 6.4, use {@see self::getWrappedExceptions()} instead
51+
*/
4752
public function getExceptions(): array
4853
{
54+
trigger_deprecation('symfony/messenger', '6.4', 'The "%s()" method is deprecated, use "%s::getWrappedExceptions" instead.', __METHOD__, self::class);
55+
4956
return $this->exceptions;
5057
}
5158

src/Symfony/Component/Messenger/Exception/HandlerFailedException.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313

1414
use Symfony\Component\Messenger\Envelope;
1515

16-
class HandlerFailedException extends RuntimeException
16+
class HandlerFailedException extends RuntimeException implements WrappedExceptionsInterface
1717
{
18+
use WrappedExceptionsTrait;
19+
1820
private array $exceptions;
1921
private Envelope $envelope;
2022

@@ -46,15 +48,24 @@ public function getEnvelope(): Envelope
4648
}
4749

4850
/**
51+
* @deprecated since Symfony 6.4, use {@see self::getWrappedExceptions()} instead
52+
*
4953
* @return \Throwable[]
5054
*/
5155
public function getNestedExceptions(): array
5256
{
57+
trigger_deprecation('symfony/messenger', '6.4', 'The "%s()" method is deprecated, use "%s::getWrappedExceptions" instead.', __METHOD__, self::class);
58+
5359
return $this->exceptions;
5460
}
5561

62+
/**
63+
* @deprecated since Symfony 6.4, use {@see self::getWrappedExceptions()} instead
64+
*/
5665
public function getNestedExceptionOfClass(string $exceptionClassName): array
5766
{
67+
trigger_deprecation('symfony/messenger', '6.4', 'The "%s()" method is deprecated, use "%s::getWrappedExceptions" instead.', __METHOD__, self::class);
68+
5869
return array_values(
5970
array_filter(
6071
$this->exceptions,
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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\Exception;
13+
14+
/**
15+
* Exception that holds multiple exceptions thrown by one or more handlers and/or messages.
16+
*
17+
* @author Jeroen <https://github.com/Jeroeny>
18+
*/
19+
interface WrappedExceptionsInterface
20+
{
21+
/**
22+
* @return \Throwable[]
23+
*/
24+
public function getWrappedExceptions(string $class = null, bool $recursive = false): array;
25+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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\Exception;
13+
14+
/**
15+
* @author Jeroen <https://github.com/Jeroeny>
16+
*
17+
* @internal
18+
*/
19+
trait WrappedExceptionsTrait
20+
{
21+
/**
22+
* @return \Throwable[]
23+
*/
24+
public function getWrappedExceptions(string $class = null, bool $recursive = false): array
25+
{
26+
return $this->getWrappedExceptionsRecursively($class, $recursive, $this->exceptions);
27+
}
28+
29+
/**
30+
* @param class-string<\Throwable>|null $class
31+
* @param iterable<\Throwable> $exceptions
32+
*
33+
* @return \Throwable[]
34+
*/
35+
private function getWrappedExceptionsRecursively(?string $class, bool $recursive, iterable $exceptions): array
36+
{
37+
$unwrapped = [];
38+
foreach ($exceptions as $key => $exception) {
39+
if ($recursive && $exception instanceof WrappedExceptionsInterface) {
40+
$unwrapped[] = $this->getWrappedExceptionsRecursively($class, $recursive, $exception->getWrappedExceptions());
41+
42+
continue;
43+
}
44+
45+
if ($class && !is_a($exception, $class)) {
46+
continue;
47+
}
48+
49+
$unwrapped[] = [$key => $exception];
50+
}
51+
52+
return array_merge(...$unwrapped);
53+
}
54+
}

src/Symfony/Component/Messenger/Tests/Exception/HandlerFailedExceptionTest.php

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Component\Messenger\Envelope;
16+
use Symfony\Component\Messenger\Exception\DelayedMessageHandlingException;
1617
use Symfony\Component\Messenger\Exception\HandlerFailedException;
1718
use Symfony\Component\Messenger\Tests\Fixtures\MyOwnChildException;
1819
use Symfony\Component\Messenger\Tests\Fixtures\MyOwnException;
@@ -32,7 +33,7 @@ public function __construct()
3233
};
3334

3435
$handlerException = new HandlerFailedException($envelope, [$exception]);
35-
$originalException = $handlerException->getNestedExceptions()[0];
36+
$originalException = $handlerException->getWrappedExceptions()[0];
3637

3738
$this->assertIsInt($handlerException->getCode(), 'Exception codes must converts to int');
3839
$this->assertSame(0, $handlerException->getCode(), 'String code (HY000) converted to int must be 0');
@@ -46,7 +47,7 @@ public function testThatNestedExceptionClassAreFound()
4647
$exception = new MyOwnException();
4748

4849
$handlerException = new HandlerFailedException($envelope, [new \LogicException(), $exception]);
49-
$this->assertSame([$exception], $handlerException->getNestedExceptionOfClass(MyOwnException::class));
50+
$this->assertSame([$exception], $handlerException->getWrappedExceptions(MyOwnException::class));
5051
}
5152

5253
public function testThatNestedExceptionClassAreFoundWhenUsingChildException()
@@ -55,7 +56,7 @@ public function testThatNestedExceptionClassAreFoundWhenUsingChildException()
5556
$exception = new MyOwnChildException();
5657

5758
$handlerException = new HandlerFailedException($envelope, [$exception]);
58-
$this->assertSame([$exception], $handlerException->getNestedExceptionOfClass(MyOwnException::class));
59+
$this->assertSame([$exception], $handlerException->getWrappedExceptions(MyOwnException::class));
5960
}
6061

6162
public function testThatNestedExceptionClassAreNotFoundIfNotPresent()
@@ -64,6 +65,39 @@ public function testThatNestedExceptionClassAreNotFoundIfNotPresent()
6465
$exception = new \LogicException();
6566

6667
$handlerException = new HandlerFailedException($envelope, [$exception]);
67-
$this->assertCount(0, $handlerException->getNestedExceptionOfClass(MyOwnException::class));
68+
$this->assertCount(0, $handlerException->getWrappedExceptions(MyOwnException::class));
69+
}
70+
71+
public function testThatWrappedExceptionsRecursive()
72+
{
73+
$envelope = new Envelope(new \stdClass());
74+
$exception1 = new \LogicException();
75+
$exception2 = new MyOwnException('second');
76+
$exception3 = new MyOwnException('third');
77+
78+
$handlerException = new HandlerFailedException($envelope, [$exception1, $exception2, new DelayedMessageHandlingException([$exception3])]);
79+
$this->assertSame([$exception1, $exception2, $exception3], $handlerException->getWrappedExceptions(recursive: true));
80+
}
81+
82+
public function testThatWrappedExceptionsRecursiveStringKeys()
83+
{
84+
$envelope = new Envelope(new \stdClass());
85+
$exception1 = new \LogicException();
86+
$exception2 = new MyOwnException('second');
87+
$exception3 = new MyOwnException('third');
88+
89+
$handlerException = new HandlerFailedException($envelope, ['first' => $exception1, 'second' => $exception2, new DelayedMessageHandlingException(['third' => $exception3])]);
90+
$this->assertSame(['first' => $exception1, 'second' => $exception2, 'third' => $exception3], $handlerException->getWrappedExceptions(recursive: true));
91+
}
92+
93+
public function testThatWrappedExceptionsByClassRecursive()
94+
{
95+
$envelope = new Envelope(new \stdClass());
96+
$exception1 = new \LogicException();
97+
$exception2 = new MyOwnException('second');
98+
$exception3 = new MyOwnException('third');
99+
100+
$handlerException = new HandlerFailedException($envelope, [$exception1, $exception2, new DelayedMessageHandlingException([$exception3])]);
101+
$this->assertSame([$exception2, $exception3], $handlerException->getWrappedExceptions(class: MyOwnException::class, recursive: true));
68102
}
69103
}

src/Symfony/Component/Notifier/EventListener/SendFailedMessageToNotifierListener.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ public function onMessageFailed(WorkerMessageFailedEvent $event)
4242

4343
$throwable = $event->getThrowable();
4444
if ($throwable instanceof HandlerFailedException) {
45-
$throwable = $throwable->getNestedExceptions()[0];
45+
$exceptions = $throwable->getWrappedExceptions();
46+
$throwable = $exceptions[array_key_first($exceptions)];
4647
}
4748
$envelope = $event->getEnvelope();
4849
$notification = Notification::fromThrowable($throwable)->importance(Notification::IMPORTANCE_HIGH);

0 commit comments

Comments
 (0)
0