Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 12f8631

Browse filesBrowse files
committed
feature #37087 [Messenger] Add FlattenException Normalizer (monteiro)
This PR was squashed before being merged into the 5.2-dev branch. Discussion ---------- [Messenger] Add FlattenException Normalizer | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes <!-- please update src/**/CHANGELOG.md files --> | Deprecations? | no <!-- please update UPGRADE-*.md and src/**/CHANGELOG.md files --> | Tickets | Fix #32719 ... <!-- prefix each issue number with "Fix #", if any --> | License | MIT | Doc PR | No docs ... <!-- required for new features --> <!-- Replace this notice by a short README for your feature/bugfix. This will help people understand your PR and can be used as a start for the documentation. Additionally (see https://symfony.com/releases): - Always add tests and ensure they pass. - Never break backward compatibility (see https://symfony.com/bc). - Bug fixes must be submitted against the lowest maintained branch where they apply (lowest branches are regularly merged to upper ones so they get the fixes too.) - Features and deprecations must be submitted against branch master. --> I have just duplicated the PR #33650 (from @skalpa) but using the new component ErrorHandler and removing the priority. Project that reproduces the bug on Symfony 5.2: https://github.com/monteiro/serializer-pr37087 (all steps are on the README). Since this adds a new class and changes behavior, we add this new feature on the 5.2 branch. Commits ------- 78fbd0a [Messenger] Add FlattenException Normalizer
2 parents a8abd81 + 78fbd0a commit 12f8631
Copy full SHA for 12f8631

File tree

6 files changed

+252
-4
lines changed
Filter options

6 files changed

+252
-4
lines changed

‎src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
use Symfony\Component\Messenger\RoutableMessageBus;
3333
use Symfony\Component\Messenger\Transport\InMemoryTransportFactory;
3434
use Symfony\Component\Messenger\Transport\Sender\SendersLocator;
35+
use Symfony\Component\Messenger\Transport\Serialization\Normalizer\FlattenExceptionNormalizer;
3536
use Symfony\Component\Messenger\Transport\Serialization\PhpSerializer;
3637
use Symfony\Component\Messenger\Transport\Serialization\Serializer;
3738
use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
@@ -64,6 +65,9 @@
6465
abstract_arg('context'),
6566
])
6667

68+
->set('serializer.normalizer.flatten_exception', FlattenExceptionNormalizer::class)
69+
->tag('serializer.normalizer', ['priority' => -880])
70+
6771
->set('messenger.transport.native_php_serializer', PhpSerializer::class)
6872

6973
// Middleware

‎src/Symfony/Component/Messenger/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Component/Messenger/CHANGELOG.md
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
5.2.0
5+
-----
6+
7+
* 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.
8+
49
5.1.0
510
-----
611

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
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\Tests\Transport\Serialization\Normalizer;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\ErrorHandler\Exception\FlattenException;
16+
use Symfony\Component\Messenger\Transport\Serialization\Normalizer\FlattenExceptionNormalizer;
17+
use Symfony\Component\Messenger\Transport\Serialization\Serializer;
18+
19+
/**
20+
* @author Pascal Luna <skalpa@zetareticuli.org>
21+
*/
22+
class FlattenExceptionNormalizerTest extends TestCase
23+
{
24+
/**
25+
* @var FlattenExceptionNormalizer
26+
*/
27+
private $normalizer;
28+
29+
protected function setUp(): void
30+
{
31+
$this->normalizer = new FlattenExceptionNormalizer();
32+
}
33+
34+
public function testSupportsNormalization()
35+
{
36+
$this->assertTrue($this->normalizer->supportsNormalization(new FlattenException(), null, $this->getMessengerContext()));
37+
$this->assertFalse($this->normalizer->supportsNormalization(new FlattenException()));
38+
$this->assertFalse($this->normalizer->supportsNormalization(new \stdClass()));
39+
}
40+
41+
/**
42+
* @dataProvider provideFlattenException
43+
*/
44+
public function testNormalize(FlattenException $exception)
45+
{
46+
$normalized = $this->normalizer->normalize($exception, null, $this->getMessengerContext());
47+
$previous = null === $exception->getPrevious() ? null : $this->normalizer->normalize($exception->getPrevious());
48+
49+
$this->assertSame($exception->getMessage(), $normalized['message']);
50+
$this->assertSame($exception->getCode(), $normalized['code']);
51+
if (null !== $exception->getStatusCode()) {
52+
$this->assertSame($exception->getStatusCode(), $normalized['status']);
53+
} else {
54+
$this->assertArrayNotHasKey('status', $normalized);
55+
}
56+
$this->assertSame($exception->getHeaders(), $normalized['headers']);
57+
$this->assertSame($exception->getClass(), $normalized['class']);
58+
$this->assertSame($exception->getFile(), $normalized['file']);
59+
$this->assertSame($exception->getLine(), $normalized['line']);
60+
$this->assertSame($previous, $normalized['previous']);
61+
$this->assertSame($exception->getTrace(), $normalized['trace']);
62+
$this->assertSame($exception->getTraceAsString(), $normalized['trace_as_string']);
63+
}
64+
65+
public function provideFlattenException(): array
66+
{
67+
return [
68+
'instance from exception' => [FlattenException::createFromThrowable(new \RuntimeException('foo', 42))],
69+
'instance with previous exception' => [FlattenException::createFromThrowable(new \RuntimeException('foo', 42, new \Exception()))],
70+
'instance with headers' => [FlattenException::createFromThrowable(new \RuntimeException('foo', 42), 404, ['Foo' => 'Bar'])],
71+
];
72+
}
73+
74+
public function testSupportsDenormalization()
75+
{
76+
$this->assertFalse($this->normalizer->supportsDenormalization(null, FlattenException::class));
77+
$this->assertTrue($this->normalizer->supportsDenormalization(null, FlattenException::class, null, $this->getMessengerContext()));
78+
$this->assertFalse($this->normalizer->supportsDenormalization(null, \stdClass::class));
79+
}
80+
81+
public function testDenormalizeValidData()
82+
{
83+
$normalized = [
84+
'message' => 'Something went foobar.',
85+
'code' => 42,
86+
'status' => 404,
87+
'headers' => ['Content-Type' => 'application/json'],
88+
'class' => static::class,
89+
'file' => 'foo.php',
90+
'line' => 123,
91+
'previous' => [
92+
'message' => 'Previous exception',
93+
'code' => 0,
94+
'class' => FlattenException::class,
95+
'file' => 'foo.php',
96+
'line' => 123,
97+
'headers' => ['Content-Type' => 'application/json'],
98+
'trace' => [
99+
[
100+
'namespace' => '', 'short_class' => '', 'class' => '', 'type' => '', 'function' => '', 'file' => 'foo.php', 'line' => 123, 'args' => [],
101+
],
102+
],
103+
'trace_as_string' => '#0 foo.php(123): foo()'.PHP_EOL.'#1 bar.php(456): bar()',
104+
],
105+
'trace' => [
106+
[
107+
'namespace' => '', 'short_class' => '', 'class' => '', 'type' => '', 'function' => '', 'file' => 'foo.php', 'line' => 123, 'args' => [],
108+
],
109+
],
110+
'trace_as_string' => '#0 foo.php(123): foo()'.PHP_EOL.'#1 bar.php(456): bar()',
111+
];
112+
$exception = $this->normalizer->denormalize($normalized, FlattenException::class);
113+
114+
$this->assertInstanceOf(FlattenException::class, $exception);
115+
$this->assertSame($normalized['message'], $exception->getMessage());
116+
$this->assertSame($normalized['code'], $exception->getCode());
117+
$this->assertSame($normalized['status'], $exception->getStatusCode());
118+
$this->assertSame($normalized['headers'], $exception->getHeaders());
119+
$this->assertSame($normalized['class'], $exception->getClass());
120+
$this->assertSame($normalized['file'], $exception->getFile());
121+
$this->assertSame($normalized['line'], $exception->getLine());
122+
$this->assertSame($normalized['trace'], $exception->getTrace());
123+
$this->assertSame($normalized['trace_as_string'], $exception->getTraceAsString());
124+
125+
$this->assertInstanceOf(FlattenException::class, $previous = $exception->getPrevious());
126+
$this->assertSame($normalized['previous']['message'], $previous->getMessage());
127+
$this->assertSame($normalized['previous']['code'], $previous->getCode());
128+
}
129+
130+
private function getMessengerContext(): array
131+
{
132+
return [
133+
Serializer::MESSENGER_SERIALIZATION_CONTEXT => true,
134+
];
135+
}
136+
}

‎src/Symfony/Component/Messenger/Tests/Transport/Serialization/SerializerTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Messenger/Tests/Transport/Serialization/SerializerTest.php
+5-3Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ public function testUsesTheCustomFormatAndContext()
6464
$message = new DummyMessage('Foo');
6565

6666
$serializer = $this->getMockBuilder(SerializerComponent\SerializerInterface::class)->getMock();
67-
$serializer->expects($this->once())->method('serialize')->with($message, 'csv', ['foo' => 'bar'])->willReturn('Yay');
68-
$serializer->expects($this->once())->method('deserialize')->with('Yay', DummyMessage::class, 'csv', ['foo' => 'bar'])->willReturn($message);
67+
$serializer->expects($this->once())->method('serialize')->with($message, 'csv', ['foo' => 'bar', Serializer::MESSENGER_SERIALIZATION_CONTEXT => true])->willReturn('Yay');
68+
$serializer->expects($this->once())->method('deserialize')->with('Yay', DummyMessage::class, 'csv', ['foo' => 'bar', Serializer::MESSENGER_SERIALIZATION_CONTEXT => true])->willReturn($message);
6969

7070
$encoder = new Serializer($serializer, 'csv', ['foo' => 'bar']);
7171

@@ -94,6 +94,7 @@ public function testEncodedWithSymfonySerializerForStamps()
9494
[$this->anything()],
9595
[$message, 'json', [
9696
ObjectNormalizer::GROUPS => ['foo'],
97+
Serializer::MESSENGER_SERIALIZATION_CONTEXT => true,
9798
]]
9899
)
99100
;
@@ -117,9 +118,10 @@ public function testDecodeWithSymfonySerializerStamp()
117118
->expects($this->exactly(2))
118119
->method('deserialize')
119120
->withConsecutive(
120-
['[{"context":{"groups":["foo"]}}]', SerializerStamp::class.'[]', 'json', []],
121+
['[{"context":{"groups":["foo"]}}]', SerializerStamp::class.'[]', 'json', [Serializer::MESSENGER_SERIALIZATION_CONTEXT => true]],
121122
['{}', DummyMessage::class, 'json', [
122123
ObjectNormalizer::GROUPS => ['foo'],
124+
Serializer::MESSENGER_SERIALIZATION_CONTEXT => true,
123125
]]
124126
)
125127
->willReturnOnConsecutiveCalls(
+100Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
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\Transport\Serialization\Normalizer;
13+
14+
use Symfony\Component\ErrorHandler\Exception\FlattenException;
15+
use Symfony\Component\Messenger\Transport\Serialization\Serializer;
16+
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
17+
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
18+
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
19+
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
20+
21+
/**
22+
* This normalizer is only used in Debug/Dev/Messenger contexts.
23+
*
24+
* @author Pascal Luna <skalpa@zetareticuli.org>
25+
*/
26+
final class FlattenExceptionNormalizer implements DenormalizerInterface, ContextAwareNormalizerInterface
27+
{
28+
use NormalizerAwareTrait;
29+
30+
/**
31+
* {@inheritdoc}
32+
*
33+
* @throws InvalidArgumentException
34+
*/
35+
public function normalize($object, $format = null, array $context = [])
36+
{
37+
$normalized = [
38+
'message' => $object->getMessage(),
39+
'code' => $object->getCode(),
40+
'headers' => $object->getHeaders(),
41+
'class' => $object->getClass(),
42+
'file' => $object->getFile(),
43+
'line' => $object->getLine(),
44+
'previous' => null === $object->getPrevious() ? null : $this->normalize($object->getPrevious(), $format, $context),
45+
'trace' => $object->getTrace(),
46+
'trace_as_string' => $object->getTraceAsString(),
47+
];
48+
if (null !== $status = $object->getStatusCode()) {
49+
$normalized['status'] = $status;
50+
}
51+
52+
return $normalized;
53+
}
54+
55+
/**
56+
* {@inheritdoc}
57+
*/
58+
public function supportsNormalization($data, $format = null, array $context = [])
59+
{
60+
return $data instanceof FlattenException && ($context[Serializer::MESSENGER_SERIALIZATION_CONTEXT] ?? false);
61+
}
62+
63+
/**
64+
* {@inheritdoc}
65+
*/
66+
public function denormalize($data, $type, $format = null, array $context = [])
67+
{
68+
$object = new FlattenException();
69+
70+
$object->setMessage($data['message']);
71+
$object->setCode($data['code']);
72+
$object->setStatusCode($data['status'] ?? null);
73+
$object->setClass($data['class']);
74+
$object->setFile($data['file']);
75+
$object->setLine($data['line']);
76+
$object->setHeaders((array) $data['headers']);
77+
78+
if (isset($data['previous'])) {
79+
$object->setPrevious($this->denormalize($data['previous'], $type, $format, $context));
80+
}
81+
82+
$property = new \ReflectionProperty(FlattenException::class, 'trace');
83+
$property->setAccessible(true);
84+
$property->setValue($object, (array) $data['trace']);
85+
86+
$property = new \ReflectionProperty(FlattenException::class, 'traceAsString');
87+
$property->setAccessible(true);
88+
$property->setValue($object, $data['trace_as_string']);
89+
90+
return $object;
91+
}
92+
93+
/**
94+
* {@inheritdoc}
95+
*/
96+
public function supportsDenormalization($data, $type, $format = null, array $context = [])
97+
{
98+
return FlattenException::class === $type && ($context[Serializer::MESSENGER_SERIALIZATION_CONTEXT] ?? false);
99+
}
100+
}

‎src/Symfony/Component/Messenger/Transport/Serialization/Serializer.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Messenger/Transport/Serialization/Serializer.php
+2-1Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
*/
3131
class Serializer implements SerializerInterface
3232
{
33+
public const MESSENGER_SERIALIZATION_CONTEXT = 'messenger_serialization';
3334
private const STAMP_HEADER_PREFIX = 'X-Message-Stamp-';
3435

3536
private $serializer;
@@ -40,7 +41,7 @@ public function __construct(SymfonySerializerInterface $serializer = null, strin
4041
{
4142
$this->serializer = $serializer ?? self::create()->serializer;
4243
$this->format = $format;
43-
$this->context = $context;
44+
$this->context = $context + [self::MESSENGER_SERIALIZATION_CONTEXT => true];
4445
}
4546

4647
public static function create(): self

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.