diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php index 05939162d8f7c..3d99912972271 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php @@ -32,6 +32,7 @@ use Symfony\Component\Messenger\RoutableMessageBus; use Symfony\Component\Messenger\Transport\InMemoryTransportFactory; use Symfony\Component\Messenger\Transport\Sender\SendersLocator; +use Symfony\Component\Messenger\Transport\Serialization\Normalizer\FlattenExceptionNormalizer; use Symfony\Component\Messenger\Transport\Serialization\PhpSerializer; use Symfony\Component\Messenger\Transport\Serialization\Serializer; use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface; @@ -64,6 +65,9 @@ abstract_arg('context'), ]) + ->set('serializer.normalizer.flatten_exception', FlattenExceptionNormalizer::class) + ->tag('serializer.normalizer', ['priority' => -880]) + ->set('messenger.transport.native_php_serializer', PhpSerializer::class) // Middleware diff --git a/src/Symfony/Component/Messenger/CHANGELOG.md b/src/Symfony/Component/Messenger/CHANGELOG.md index d2f6b000005e2..bdc428446a49b 100644 --- a/src/Symfony/Component/Messenger/CHANGELOG.md +++ b/src/Symfony/Component/Messenger/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.2.0 +----- + +* Added `FlattenExceptionNormalizer` to give more information about the exception on Messenger background processes. The `FlattenExceptionNormalizer` has a higher priority than `ProblemNormalizer` and it is only used when the Messenger serialization context is set. + 5.1.0 ----- diff --git a/src/Symfony/Component/Messenger/Tests/Transport/Serialization/Normalizer/FlattenExceptionNormalizerTest.php b/src/Symfony/Component/Messenger/Tests/Transport/Serialization/Normalizer/FlattenExceptionNormalizerTest.php new file mode 100644 index 0000000000000..516905a96025c --- /dev/null +++ b/src/Symfony/Component/Messenger/Tests/Transport/Serialization/Normalizer/FlattenExceptionNormalizerTest.php @@ -0,0 +1,136 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Messenger\Tests\Transport\Serialization\Normalizer; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\ErrorHandler\Exception\FlattenException; +use Symfony\Component\Messenger\Transport\Serialization\Normalizer\FlattenExceptionNormalizer; +use Symfony\Component\Messenger\Transport\Serialization\Serializer; + +/** + * @author Pascal Luna + */ +class FlattenExceptionNormalizerTest extends TestCase +{ + /** + * @var FlattenExceptionNormalizer + */ + private $normalizer; + + protected function setUp(): void + { + $this->normalizer = new FlattenExceptionNormalizer(); + } + + public function testSupportsNormalization() + { + $this->assertTrue($this->normalizer->supportsNormalization(new FlattenException(), null, $this->getMessengerContext())); + $this->assertFalse($this->normalizer->supportsNormalization(new FlattenException())); + $this->assertFalse($this->normalizer->supportsNormalization(new \stdClass())); + } + + /** + * @dataProvider provideFlattenException + */ + public function testNormalize(FlattenException $exception) + { + $normalized = $this->normalizer->normalize($exception, null, $this->getMessengerContext()); + $previous = null === $exception->getPrevious() ? null : $this->normalizer->normalize($exception->getPrevious()); + + $this->assertSame($exception->getMessage(), $normalized['message']); + $this->assertSame($exception->getCode(), $normalized['code']); + if (null !== $exception->getStatusCode()) { + $this->assertSame($exception->getStatusCode(), $normalized['status']); + } else { + $this->assertArrayNotHasKey('status', $normalized); + } + $this->assertSame($exception->getHeaders(), $normalized['headers']); + $this->assertSame($exception->getClass(), $normalized['class']); + $this->assertSame($exception->getFile(), $normalized['file']); + $this->assertSame($exception->getLine(), $normalized['line']); + $this->assertSame($previous, $normalized['previous']); + $this->assertSame($exception->getTrace(), $normalized['trace']); + $this->assertSame($exception->getTraceAsString(), $normalized['trace_as_string']); + } + + public function provideFlattenException(): array + { + return [ + 'instance from exception' => [FlattenException::createFromThrowable(new \RuntimeException('foo', 42))], + 'instance with previous exception' => [FlattenException::createFromThrowable(new \RuntimeException('foo', 42, new \Exception()))], + 'instance with headers' => [FlattenException::createFromThrowable(new \RuntimeException('foo', 42), 404, ['Foo' => 'Bar'])], + ]; + } + + public function testSupportsDenormalization() + { + $this->assertFalse($this->normalizer->supportsDenormalization(null, FlattenException::class)); + $this->assertTrue($this->normalizer->supportsDenormalization(null, FlattenException::class, null, $this->getMessengerContext())); + $this->assertFalse($this->normalizer->supportsDenormalization(null, \stdClass::class)); + } + + public function testDenormalizeValidData() + { + $normalized = [ + 'message' => 'Something went foobar.', + 'code' => 42, + 'status' => 404, + 'headers' => ['Content-Type' => 'application/json'], + 'class' => static::class, + 'file' => 'foo.php', + 'line' => 123, + 'previous' => [ + 'message' => 'Previous exception', + 'code' => 0, + 'class' => FlattenException::class, + 'file' => 'foo.php', + 'line' => 123, + 'headers' => ['Content-Type' => 'application/json'], + 'trace' => [ + [ + 'namespace' => '', 'short_class' => '', 'class' => '', 'type' => '', 'function' => '', 'file' => 'foo.php', 'line' => 123, 'args' => [], + ], + ], + 'trace_as_string' => '#0 foo.php(123): foo()'.PHP_EOL.'#1 bar.php(456): bar()', + ], + 'trace' => [ + [ + 'namespace' => '', 'short_class' => '', 'class' => '', 'type' => '', 'function' => '', 'file' => 'foo.php', 'line' => 123, 'args' => [], + ], + ], + 'trace_as_string' => '#0 foo.php(123): foo()'.PHP_EOL.'#1 bar.php(456): bar()', + ]; + $exception = $this->normalizer->denormalize($normalized, FlattenException::class); + + $this->assertInstanceOf(FlattenException::class, $exception); + $this->assertSame($normalized['message'], $exception->getMessage()); + $this->assertSame($normalized['code'], $exception->getCode()); + $this->assertSame($normalized['status'], $exception->getStatusCode()); + $this->assertSame($normalized['headers'], $exception->getHeaders()); + $this->assertSame($normalized['class'], $exception->getClass()); + $this->assertSame($normalized['file'], $exception->getFile()); + $this->assertSame($normalized['line'], $exception->getLine()); + $this->assertSame($normalized['trace'], $exception->getTrace()); + $this->assertSame($normalized['trace_as_string'], $exception->getTraceAsString()); + + $this->assertInstanceOf(FlattenException::class, $previous = $exception->getPrevious()); + $this->assertSame($normalized['previous']['message'], $previous->getMessage()); + $this->assertSame($normalized['previous']['code'], $previous->getCode()); + } + + private function getMessengerContext(): array + { + return [ + Serializer::MESSENGER_SERIALIZATION_CONTEXT => true, + ]; + } +} diff --git a/src/Symfony/Component/Messenger/Tests/Transport/Serialization/SerializerTest.php b/src/Symfony/Component/Messenger/Tests/Transport/Serialization/SerializerTest.php index adff357dc91ba..b6714d3d409fe 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/Serialization/SerializerTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/Serialization/SerializerTest.php @@ -64,8 +64,8 @@ public function testUsesTheCustomFormatAndContext() $message = new DummyMessage('Foo'); $serializer = $this->getMockBuilder(SerializerComponent\SerializerInterface::class)->getMock(); - $serializer->expects($this->once())->method('serialize')->with($message, 'csv', ['foo' => 'bar'])->willReturn('Yay'); - $serializer->expects($this->once())->method('deserialize')->with('Yay', DummyMessage::class, 'csv', ['foo' => 'bar'])->willReturn($message); + $serializer->expects($this->once())->method('serialize')->with($message, 'csv', ['foo' => 'bar', Serializer::MESSENGER_SERIALIZATION_CONTEXT => true])->willReturn('Yay'); + $serializer->expects($this->once())->method('deserialize')->with('Yay', DummyMessage::class, 'csv', ['foo' => 'bar', Serializer::MESSENGER_SERIALIZATION_CONTEXT => true])->willReturn($message); $encoder = new Serializer($serializer, 'csv', ['foo' => 'bar']); @@ -94,6 +94,7 @@ public function testEncodedWithSymfonySerializerForStamps() [$this->anything()], [$message, 'json', [ ObjectNormalizer::GROUPS => ['foo'], + Serializer::MESSENGER_SERIALIZATION_CONTEXT => true, ]] ) ; @@ -117,9 +118,10 @@ public function testDecodeWithSymfonySerializerStamp() ->expects($this->exactly(2)) ->method('deserialize') ->withConsecutive( - ['[{"context":{"groups":["foo"]}}]', SerializerStamp::class.'[]', 'json', []], + ['[{"context":{"groups":["foo"]}}]', SerializerStamp::class.'[]', 'json', [Serializer::MESSENGER_SERIALIZATION_CONTEXT => true]], ['{}', DummyMessage::class, 'json', [ ObjectNormalizer::GROUPS => ['foo'], + Serializer::MESSENGER_SERIALIZATION_CONTEXT => true, ]] ) ->willReturnOnConsecutiveCalls( diff --git a/src/Symfony/Component/Messenger/Transport/Serialization/Normalizer/FlattenExceptionNormalizer.php b/src/Symfony/Component/Messenger/Transport/Serialization/Normalizer/FlattenExceptionNormalizer.php new file mode 100644 index 0000000000000..6ee46c05a6ab3 --- /dev/null +++ b/src/Symfony/Component/Messenger/Transport/Serialization/Normalizer/FlattenExceptionNormalizer.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Messenger\Transport\Serialization\Normalizer; + +use Symfony\Component\ErrorHandler\Exception\FlattenException; +use Symfony\Component\Messenger\Transport\Serialization\Serializer; +use Symfony\Component\Serializer\Exception\InvalidArgumentException; +use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface; +use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; +use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait; + +/** + * This normalizer is only used in Debug/Dev/Messenger contexts. + * + * @author Pascal Luna + */ +final class FlattenExceptionNormalizer implements DenormalizerInterface, ContextAwareNormalizerInterface +{ + use NormalizerAwareTrait; + + /** + * {@inheritdoc} + * + * @throws InvalidArgumentException + */ + public function normalize($object, $format = null, array $context = []) + { + $normalized = [ + 'message' => $object->getMessage(), + 'code' => $object->getCode(), + 'headers' => $object->getHeaders(), + 'class' => $object->getClass(), + 'file' => $object->getFile(), + 'line' => $object->getLine(), + 'previous' => null === $object->getPrevious() ? null : $this->normalize($object->getPrevious(), $format, $context), + 'trace' => $object->getTrace(), + 'trace_as_string' => $object->getTraceAsString(), + ]; + if (null !== $status = $object->getStatusCode()) { + $normalized['status'] = $status; + } + + return $normalized; + } + + /** + * {@inheritdoc} + */ + public function supportsNormalization($data, $format = null, array $context = []) + { + return $data instanceof FlattenException && ($context[Serializer::MESSENGER_SERIALIZATION_CONTEXT] ?? false); + } + + /** + * {@inheritdoc} + */ + public function denormalize($data, $type, $format = null, array $context = []) + { + $object = new FlattenException(); + + $object->setMessage($data['message']); + $object->setCode($data['code']); + $object->setStatusCode($data['status'] ?? null); + $object->setClass($data['class']); + $object->setFile($data['file']); + $object->setLine($data['line']); + $object->setHeaders((array) $data['headers']); + + if (isset($data['previous'])) { + $object->setPrevious($this->denormalize($data['previous'], $type, $format, $context)); + } + + $property = new \ReflectionProperty(FlattenException::class, 'trace'); + $property->setAccessible(true); + $property->setValue($object, (array) $data['trace']); + + $property = new \ReflectionProperty(FlattenException::class, 'traceAsString'); + $property->setAccessible(true); + $property->setValue($object, $data['trace_as_string']); + + return $object; + } + + /** + * {@inheritdoc} + */ + public function supportsDenormalization($data, $type, $format = null, array $context = []) + { + return FlattenException::class === $type && ($context[Serializer::MESSENGER_SERIALIZATION_CONTEXT] ?? false); + } +} diff --git a/src/Symfony/Component/Messenger/Transport/Serialization/Serializer.php b/src/Symfony/Component/Messenger/Transport/Serialization/Serializer.php index 22d48f7e23012..378cdd4821dff 100644 --- a/src/Symfony/Component/Messenger/Transport/Serialization/Serializer.php +++ b/src/Symfony/Component/Messenger/Transport/Serialization/Serializer.php @@ -30,6 +30,7 @@ */ class Serializer implements SerializerInterface { + public const MESSENGER_SERIALIZATION_CONTEXT = 'messenger_serialization'; private const STAMP_HEADER_PREFIX = 'X-Message-Stamp-'; private $serializer; @@ -40,7 +41,7 @@ public function __construct(SymfonySerializerInterface $serializer = null, strin { $this->serializer = $serializer ?? self::create()->serializer; $this->format = $format; - $this->context = $context; + $this->context = $context + [self::MESSENGER_SERIALIZATION_CONTEXT => true]; } public static function create(): self 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