diff --git a/UPGRADE-6.4.md b/UPGRADE-6.4.md index 280d06a073b5..8c1e00db6e9e 100644 --- a/UPGRADE-6.4.md +++ b/UPGRADE-6.4.md @@ -113,6 +113,7 @@ Messenger --------- * Deprecate `StopWorkerOnSignalsListener` in favor of using the `SignalableCommandInterface` + * Deprecate `HandlerFailedException::getNestedExceptions()`, `HandlerFailedException::getNestedExceptionsOfClass()` and `DelayedMessageHandlingException::getExceptions()` which are replaced by a new `getWrappedExceptions()` method MonologBridge ------------- diff --git a/src/Symfony/Bridge/Doctrine/Messenger/DoctrineTransactionMiddleware.php b/src/Symfony/Bridge/Doctrine/Messenger/DoctrineTransactionMiddleware.php index 4eb7afcc223d..e4831557f01d 100644 --- a/src/Symfony/Bridge/Doctrine/Messenger/DoctrineTransactionMiddleware.php +++ b/src/Symfony/Bridge/Doctrine/Messenger/DoctrineTransactionMiddleware.php @@ -39,7 +39,7 @@ protected function handleForManager(EntityManagerInterface $entityManager, Envel if ($exception instanceof HandlerFailedException) { // Remove all HandledStamp from the envelope so the retry will execute all handlers again. // When a handler fails, the queries of allegedly successful previous handlers just got rolled back. - throw new HandlerFailedException($exception->getEnvelope()->withoutAll(HandledStamp::class), $exception->getNestedExceptions()); + throw new HandlerFailedException($exception->getEnvelope()->withoutAll(HandledStamp::class), $exception->getWrappedExceptions()); } throw $exception; diff --git a/src/Symfony/Component/Mailer/Mailer.php b/src/Symfony/Component/Mailer/Mailer.php index cd305a65c492..5319dbef8132 100644 --- a/src/Symfony/Component/Mailer/Mailer.php +++ b/src/Symfony/Component/Mailer/Mailer.php @@ -65,7 +65,7 @@ public function send(RawMessage $message, Envelope $envelope = null): void try { $this->bus->dispatch(new SendEmailMessage($message, $envelope), $stamps); } catch (HandlerFailedException $e) { - foreach ($e->getNestedExceptions() as $nested) { + foreach ($e->getWrappedExceptions() as $nested) { if ($nested instanceof TransportExceptionInterface) { throw $nested; } diff --git a/src/Symfony/Component/Messenger/CHANGELOG.md b/src/Symfony/Component/Messenger/CHANGELOG.md index 429e328c00c5..d6709044dcf1 100644 --- a/src/Symfony/Component/Messenger/CHANGELOG.md +++ b/src/Symfony/Component/Messenger/CHANGELOG.md @@ -9,6 +9,9 @@ CHANGELOG * Add support for multiple Redis Sentinel hosts * Add `--all` option to the `messenger:failed:remove` command * `RejectRedeliveredMessageException` implements `UnrecoverableExceptionInterface` in order to not be retried + * Add `WrappedExceptionsInterface` interface for exceptions that hold multiple individual exceptions + * Deprecate `HandlerFailedException::getNestedExceptions()`, `HandlerFailedException::getNestedExceptionsOfClass()` + and `DelayedMessageHandlingException::getExceptions()` which are replaced by a new `getWrappedExceptions()` method 6.3 --- diff --git a/src/Symfony/Component/Messenger/EventListener/SendFailedMessageForRetryListener.php b/src/Symfony/Component/Messenger/EventListener/SendFailedMessageForRetryListener.php index ba775b0c1784..7cebcc64d253 100644 --- a/src/Symfony/Component/Messenger/EventListener/SendFailedMessageForRetryListener.php +++ b/src/Symfony/Component/Messenger/EventListener/SendFailedMessageForRetryListener.php @@ -128,7 +128,7 @@ private function shouldRetry(\Throwable $e, Envelope $envelope, RetryStrategyInt // if ALL nested Exceptions are an instance of UnrecoverableExceptionInterface we should not retry if ($e instanceof HandlerFailedException) { $shouldNotRetry = true; - foreach ($e->getNestedExceptions() as $nestedException) { + foreach ($e->getWrappedExceptions() as $nestedException) { if ($nestedException instanceof RecoverableExceptionInterface) { return true; } diff --git a/src/Symfony/Component/Messenger/EventListener/StopWorkerOnCustomStopExceptionListener.php b/src/Symfony/Component/Messenger/EventListener/StopWorkerOnCustomStopExceptionListener.php index 8caddc78bfc9..54cdf35109d1 100644 --- a/src/Symfony/Component/Messenger/EventListener/StopWorkerOnCustomStopExceptionListener.php +++ b/src/Symfony/Component/Messenger/EventListener/StopWorkerOnCustomStopExceptionListener.php @@ -31,7 +31,7 @@ public function onMessageFailed(WorkerMessageFailedEvent $event): void $this->stop = true; } if ($th instanceof HandlerFailedException) { - foreach ($th->getNestedExceptions() as $e) { + foreach ($th->getWrappedExceptions() as $e) { if ($e instanceof StopWorkerExceptionInterface) { $this->stop = true; break; diff --git a/src/Symfony/Component/Messenger/Exception/DelayedMessageHandlingException.php b/src/Symfony/Component/Messenger/Exception/DelayedMessageHandlingException.php index 3baafda76e3b..d4534d7c7353 100644 --- a/src/Symfony/Component/Messenger/Exception/DelayedMessageHandlingException.php +++ b/src/Symfony/Component/Messenger/Exception/DelayedMessageHandlingException.php @@ -19,8 +19,10 @@ * * @author Tobias Nyholm */ -class DelayedMessageHandlingException extends RuntimeException +class DelayedMessageHandlingException extends RuntimeException implements WrappedExceptionsInterface { + use WrappedExceptionsTrait; + private array $exceptions; private Envelope $envelope; @@ -41,11 +43,16 @@ public function __construct(array $exceptions, Envelope $envelope) $this->exceptions = $exceptions; - parent::__construct($message, 0, $exceptions[0]); + parent::__construct($message, 0, $exceptions[array_key_first($exceptions)]); } + /** + * @deprecated since Symfony 6.4, use {@see self::getWrappedExceptions()} instead + */ public function getExceptions(): array { + trigger_deprecation('symfony/messenger', '6.4', 'The "%s()" method is deprecated, use "%s::getWrappedExceptions" instead.', __METHOD__, self::class); + return $this->exceptions; } diff --git a/src/Symfony/Component/Messenger/Exception/HandlerFailedException.php b/src/Symfony/Component/Messenger/Exception/HandlerFailedException.php index 05be1b89e380..1b624db91c6c 100644 --- a/src/Symfony/Component/Messenger/Exception/HandlerFailedException.php +++ b/src/Symfony/Component/Messenger/Exception/HandlerFailedException.php @@ -13,8 +13,10 @@ use Symfony\Component\Messenger\Envelope; -class HandlerFailedException extends RuntimeException +class HandlerFailedException extends RuntimeException implements WrappedExceptionsInterface { + use WrappedExceptionsTrait; + private array $exceptions; private Envelope $envelope; @@ -46,15 +48,24 @@ public function getEnvelope(): Envelope } /** + * @deprecated since Symfony 6.4, use {@see self::getWrappedExceptions()} instead + * * @return \Throwable[] */ public function getNestedExceptions(): array { + trigger_deprecation('symfony/messenger', '6.4', 'The "%s()" method is deprecated, use "%s::getWrappedExceptions" instead.', __METHOD__, self::class); + return $this->exceptions; } + /** + * @deprecated since Symfony 6.4, use {@see self::getWrappedExceptions()} instead + */ public function getNestedExceptionOfClass(string $exceptionClassName): array { + trigger_deprecation('symfony/messenger', '6.4', 'The "%s()" method is deprecated, use "%s::getWrappedExceptions" instead.', __METHOD__, self::class); + return array_values( array_filter( $this->exceptions, diff --git a/src/Symfony/Component/Messenger/Exception/WrappedExceptionsInterface.php b/src/Symfony/Component/Messenger/Exception/WrappedExceptionsInterface.php new file mode 100644 index 000000000000..845439763f38 --- /dev/null +++ b/src/Symfony/Component/Messenger/Exception/WrappedExceptionsInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Messenger\Exception; + +/** + * Exception that holds multiple exceptions thrown by one or more handlers and/or messages. + * + * @author Jeroen + */ +interface WrappedExceptionsInterface +{ + /** + * @return \Throwable[] + */ + public function getWrappedExceptions(string $class = null, bool $recursive = false): array; +} diff --git a/src/Symfony/Component/Messenger/Exception/WrappedExceptionsTrait.php b/src/Symfony/Component/Messenger/Exception/WrappedExceptionsTrait.php new file mode 100644 index 000000000000..4b6fb65c6dc7 --- /dev/null +++ b/src/Symfony/Component/Messenger/Exception/WrappedExceptionsTrait.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Messenger\Exception; + +/** + * @author Jeroen + * + * @internal + */ +trait WrappedExceptionsTrait +{ + /** + * @return \Throwable[] + */ + public function getWrappedExceptions(string $class = null, bool $recursive = false): array + { + return $this->getWrappedExceptionsRecursively($class, $recursive, $this->exceptions); + } + + /** + * @param class-string<\Throwable>|null $class + * @param iterable<\Throwable> $exceptions + * + * @return \Throwable[] + */ + private function getWrappedExceptionsRecursively(?string $class, bool $recursive, iterable $exceptions): array + { + $unwrapped = []; + foreach ($exceptions as $key => $exception) { + if ($recursive && $exception instanceof WrappedExceptionsInterface) { + $unwrapped[] = $this->getWrappedExceptionsRecursively($class, $recursive, $exception->getWrappedExceptions()); + + continue; + } + + if ($class && !is_a($exception, $class)) { + continue; + } + + $unwrapped[] = [$key => $exception]; + } + + return array_merge(...$unwrapped); + } +} diff --git a/src/Symfony/Component/Messenger/Tests/Exception/HandlerFailedExceptionTest.php b/src/Symfony/Component/Messenger/Tests/Exception/HandlerFailedExceptionTest.php index 60c3bd63a525..3130ad633ac0 100644 --- a/src/Symfony/Component/Messenger/Tests/Exception/HandlerFailedExceptionTest.php +++ b/src/Symfony/Component/Messenger/Tests/Exception/HandlerFailedExceptionTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\Exception\DelayedMessageHandlingException; use Symfony\Component\Messenger\Exception\HandlerFailedException; use Symfony\Component\Messenger\Tests\Fixtures\MyOwnChildException; use Symfony\Component\Messenger\Tests\Fixtures\MyOwnException; @@ -32,7 +33,7 @@ public function __construct() }; $handlerException = new HandlerFailedException($envelope, [$exception]); - $originalException = $handlerException->getNestedExceptions()[0]; + $originalException = $handlerException->getWrappedExceptions()[0]; $this->assertIsInt($handlerException->getCode(), 'Exception codes must converts to int'); $this->assertSame(0, $handlerException->getCode(), 'String code (HY000) converted to int must be 0'); @@ -46,7 +47,7 @@ public function testThatNestedExceptionClassAreFound() $exception = new MyOwnException(); $handlerException = new HandlerFailedException($envelope, [new \LogicException(), $exception]); - $this->assertSame([$exception], $handlerException->getNestedExceptionOfClass(MyOwnException::class)); + $this->assertSame([$exception], $handlerException->getWrappedExceptions(MyOwnException::class)); } public function testThatNestedExceptionClassAreFoundWhenUsingChildException() @@ -55,7 +56,7 @@ public function testThatNestedExceptionClassAreFoundWhenUsingChildException() $exception = new MyOwnChildException(); $handlerException = new HandlerFailedException($envelope, [$exception]); - $this->assertSame([$exception], $handlerException->getNestedExceptionOfClass(MyOwnException::class)); + $this->assertSame([$exception], $handlerException->getWrappedExceptions(MyOwnException::class)); } public function testThatNestedExceptionClassAreNotFoundIfNotPresent() @@ -64,6 +65,39 @@ public function testThatNestedExceptionClassAreNotFoundIfNotPresent() $exception = new \LogicException(); $handlerException = new HandlerFailedException($envelope, [$exception]); - $this->assertCount(0, $handlerException->getNestedExceptionOfClass(MyOwnException::class)); + $this->assertCount(0, $handlerException->getWrappedExceptions(MyOwnException::class)); + } + + public function testThatWrappedExceptionsRecursive() + { + $envelope = new Envelope(new \stdClass()); + $exception1 = new \LogicException(); + $exception2 = new MyOwnException('second'); + $exception3 = new MyOwnException('third'); + + $handlerException = new HandlerFailedException($envelope, [$exception1, $exception2, new DelayedMessageHandlingException([$exception3])]); + $this->assertSame([$exception1, $exception2, $exception3], $handlerException->getWrappedExceptions(recursive: true)); + } + + public function testThatWrappedExceptionsRecursiveStringKeys() + { + $envelope = new Envelope(new \stdClass()); + $exception1 = new \LogicException(); + $exception2 = new MyOwnException('second'); + $exception3 = new MyOwnException('third'); + + $handlerException = new HandlerFailedException($envelope, ['first' => $exception1, 'second' => $exception2, new DelayedMessageHandlingException(['third' => $exception3])]); + $this->assertSame(['first' => $exception1, 'second' => $exception2, 'third' => $exception3], $handlerException->getWrappedExceptions(recursive: true)); + } + + public function testThatWrappedExceptionsByClassRecursive() + { + $envelope = new Envelope(new \stdClass()); + $exception1 = new \LogicException(); + $exception2 = new MyOwnException('second'); + $exception3 = new MyOwnException('third'); + + $handlerException = new HandlerFailedException($envelope, [$exception1, $exception2, new DelayedMessageHandlingException([$exception3])]); + $this->assertSame([$exception2, $exception3], $handlerException->getWrappedExceptions(class: MyOwnException::class, recursive: true)); } } diff --git a/src/Symfony/Component/Notifier/EventListener/SendFailedMessageToNotifierListener.php b/src/Symfony/Component/Notifier/EventListener/SendFailedMessageToNotifierListener.php index 355c136195f5..b960429829d9 100644 --- a/src/Symfony/Component/Notifier/EventListener/SendFailedMessageToNotifierListener.php +++ b/src/Symfony/Component/Notifier/EventListener/SendFailedMessageToNotifierListener.php @@ -42,7 +42,8 @@ public function onMessageFailed(WorkerMessageFailedEvent $event) $throwable = $event->getThrowable(); if ($throwable instanceof HandlerFailedException) { - $throwable = $throwable->getNestedExceptions()[0]; + $exceptions = $throwable->getWrappedExceptions(); + $throwable = $exceptions[array_key_first($exceptions)]; } $envelope = $event->getEnvelope(); $notification = Notification::fromThrowable($throwable)->importance(Notification::IMPORTANCE_HIGH); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy