[Messenger] Add FlattenException Normalizer
This commit is contained in:
parent
1f77f32d78
commit
78fbd0ac77
@ -32,6 +32,7 @@ use Symfony\Component\Messenger\Retry\MultiplierRetryStrategy;
|
|||||||
use Symfony\Component\Messenger\RoutableMessageBus;
|
use Symfony\Component\Messenger\RoutableMessageBus;
|
||||||
use Symfony\Component\Messenger\Transport\InMemoryTransportFactory;
|
use Symfony\Component\Messenger\Transport\InMemoryTransportFactory;
|
||||||
use Symfony\Component\Messenger\Transport\Sender\SendersLocator;
|
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\PhpSerializer;
|
||||||
use Symfony\Component\Messenger\Transport\Serialization\Serializer;
|
use Symfony\Component\Messenger\Transport\Serialization\Serializer;
|
||||||
use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
|
use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
|
||||||
@ -64,6 +65,9 @@ return static function (ContainerConfigurator $container) {
|
|||||||
abstract_arg('context'),
|
abstract_arg('context'),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
->set('serializer.normalizer.flatten_exception', FlattenExceptionNormalizer::class)
|
||||||
|
->tag('serializer.normalizer', ['priority' => -880])
|
||||||
|
|
||||||
->set('messenger.transport.native_php_serializer', PhpSerializer::class)
|
->set('messenger.transport.native_php_serializer', PhpSerializer::class)
|
||||||
|
|
||||||
// Middleware
|
// Middleware
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
CHANGELOG
|
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
|
5.1.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
@ -0,0 +1,136 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* 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 <skalpa@zetareticuli.org>
|
||||||
|
*/
|
||||||
|
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,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -64,8 +64,8 @@ class SerializerTest extends TestCase
|
|||||||
$message = new DummyMessage('Foo');
|
$message = new DummyMessage('Foo');
|
||||||
|
|
||||||
$serializer = $this->getMockBuilder(SerializerComponent\SerializerInterface::class)->getMock();
|
$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('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'])->willReturn($message);
|
$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']);
|
$encoder = new Serializer($serializer, 'csv', ['foo' => 'bar']);
|
||||||
|
|
||||||
@ -94,6 +94,7 @@ class SerializerTest extends TestCase
|
|||||||
[$this->anything()],
|
[$this->anything()],
|
||||||
[$message, 'json', [
|
[$message, 'json', [
|
||||||
ObjectNormalizer::GROUPS => ['foo'],
|
ObjectNormalizer::GROUPS => ['foo'],
|
||||||
|
Serializer::MESSENGER_SERIALIZATION_CONTEXT => true,
|
||||||
]]
|
]]
|
||||||
)
|
)
|
||||||
;
|
;
|
||||||
@ -117,9 +118,10 @@ class SerializerTest extends TestCase
|
|||||||
->expects($this->exactly(2))
|
->expects($this->exactly(2))
|
||||||
->method('deserialize')
|
->method('deserialize')
|
||||||
->withConsecutive(
|
->withConsecutive(
|
||||||
['[{"context":{"groups":["foo"]}}]', SerializerStamp::class.'[]', 'json', []],
|
['[{"context":{"groups":["foo"]}}]', SerializerStamp::class.'[]', 'json', [Serializer::MESSENGER_SERIALIZATION_CONTEXT => true]],
|
||||||
['{}', DummyMessage::class, 'json', [
|
['{}', DummyMessage::class, 'json', [
|
||||||
ObjectNormalizer::GROUPS => ['foo'],
|
ObjectNormalizer::GROUPS => ['foo'],
|
||||||
|
Serializer::MESSENGER_SERIALIZATION_CONTEXT => true,
|
||||||
]]
|
]]
|
||||||
)
|
)
|
||||||
->willReturnOnConsecutiveCalls(
|
->willReturnOnConsecutiveCalls(
|
||||||
|
@ -0,0 +1,100 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* 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 <skalpa@zetareticuli.org>
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
@ -30,6 +30,7 @@ use Symfony\Component\Serializer\SerializerInterface as SymfonySerializerInterfa
|
|||||||
*/
|
*/
|
||||||
class Serializer implements SerializerInterface
|
class Serializer implements SerializerInterface
|
||||||
{
|
{
|
||||||
|
public const MESSENGER_SERIALIZATION_CONTEXT = 'messenger_serialization';
|
||||||
private const STAMP_HEADER_PREFIX = 'X-Message-Stamp-';
|
private const STAMP_HEADER_PREFIX = 'X-Message-Stamp-';
|
||||||
|
|
||||||
private $serializer;
|
private $serializer;
|
||||||
@ -40,7 +41,7 @@ class Serializer implements SerializerInterface
|
|||||||
{
|
{
|
||||||
$this->serializer = $serializer ?? self::create()->serializer;
|
$this->serializer = $serializer ?? self::create()->serializer;
|
||||||
$this->format = $format;
|
$this->format = $format;
|
||||||
$this->context = $context;
|
$this->context = $context + [self::MESSENGER_SERIALIZATION_CONTEXT => true];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function create(): self
|
public static function create(): self
|
||||||
|
Reference in New Issue
Block a user