Skip to content

Commit 733e404

Browse files
committed
Added FlattenExceptionNormalizer
1 parent 9d882e8 commit 733e404

File tree

4 files changed

+276
-0
lines changed

4 files changed

+276
-0
lines changed

src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@
3131
<service id="Symfony\Component\Serializer\Mapping\ClassDiscriminatorResolverInterface" alias="serializer.mapping.class_discriminator_resolver" />
3232

3333
<!-- Normalizer -->
34+
<service id="serializer.normalizer.flatten_exception" class="Symfony\Component\Serializer\Normalizer\FlattenExceptionNormalizer">
35+
<!-- Run before serializer.normalizer.object -->
36+
<tag name="serializer.normalizer" priority="-915" />
37+
</service>
38+
3439
<service id="serializer.normalizer.constraint_violation_list" class="Symfony\Component\Serializer\Normalizer\ConstraintViolationListNormalizer">
3540
<argument type="collection" />
3641
<argument type="service" id="serializer.name_converter.metadata_aware" />
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
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\Serializer\Normalizer;
13+
14+
use Symfony\Component\Debug\Exception\FlattenException;
15+
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
16+
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
17+
18+
/**
19+
* Normalizes FlattenException instances.
20+
*
21+
* @author Pascal Luna <skalpa@zetareticuli.org>
22+
*/
23+
class FlattenExceptionNormalizer implements NormalizerInterface, DenormalizerInterface, CacheableSupportsMethodInterface
24+
{
25+
/**
26+
* {@inheritdoc}
27+
*
28+
* @throws InvalidArgumentException
29+
*/
30+
public function normalize($object, $format = null, array $context = [])
31+
{
32+
if (!$object instanceof FlattenException) {
33+
throw new InvalidArgumentException(sprintf('The object must be an instance of %s.', FlattenException::class));
34+
}
35+
/* @var FlattenException $object */
36+
37+
return [
38+
'message' => $object->getMessage(),
39+
'code' => $object->getCode(),
40+
'status_code' => $object->getStatusCode(),
41+
'headers' => $object->getHeaders(),
42+
'class' => $object->getClass(),
43+
'file' => $object->getFile(),
44+
'line' => $object->getLine(),
45+
'previous' => null === $object->getPrevious() ? null : $this->normalize($object->getPrevious(), $format, $context),
46+
'trace' => $object->getTrace(),
47+
'trace_as_string' => $object->getTraceAsString(),
48+
];
49+
}
50+
51+
/**
52+
* {@inheritdoc}
53+
*/
54+
public function supportsNormalization($data, $format = null)
55+
{
56+
return $data instanceof FlattenException;
57+
}
58+
59+
/**
60+
* {@inheritdoc}
61+
*/
62+
public function denormalize($data, $type, $format = null, array $context = [])
63+
{
64+
if (!\is_array($data)) {
65+
throw new NotNormalizableValueException(sprintf(
66+
'The normalized data must be an array, got %s.',
67+
\is_object($data) ? \get_class($data) : \gettype($data)
68+
));
69+
}
70+
71+
$object = new FlattenException();
72+
73+
$object->setMessage($data['message'] ?? null);
74+
$object->setCode($data['code'] ?? null);
75+
$object->setStatusCode($data['status_code'] ?? null);
76+
$object->setClass($data['class'] ?? null);
77+
$object->setFile($data['file'] ?? null);
78+
$object->setLine($data['line'] ?? null);
79+
80+
if (isset($data['headers'])) {
81+
$object->setHeaders((array) $data['headers']);
82+
}
83+
if (isset($data['previous'])) {
84+
$object->setPrevious($this->denormalize($data['previous'], $type, $format, $context));
85+
}
86+
if (isset($data['trace'])) {
87+
$property = new \ReflectionProperty(FlattenException::class, 'trace');
88+
$property->setAccessible(true);
89+
$property->setValue($object, (array) $data['trace']);
90+
}
91+
if (isset($data['trace_as_string'])) {
92+
$property = new \ReflectionProperty(FlattenException::class, 'traceAsString');
93+
$property->setAccessible(true);
94+
$property->setValue($object, $data['trace_as_string']);
95+
}
96+
97+
return $object;
98+
}
99+
100+
/**
101+
* {@inheritdoc}
102+
*/
103+
public function supportsDenormalization($data, $type, $format = null, array $context = [])
104+
{
105+
return FlattenException::class === $type;
106+
}
107+
108+
/**
109+
* {@inheritdoc}
110+
*/
111+
public function hasCacheableSupportsMethod(): bool
112+
{
113+
return __CLASS__ === \get_class($this);
114+
}
115+
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
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\Serializer\Tests\Normalizer;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Debug\Exception\FlattenException;
16+
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
17+
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
18+
use Symfony\Component\Serializer\Normalizer\FlattenExceptionNormalizer;
19+
20+
/**
21+
* @author Pascal Luna <skalpa@zetareticuli.org>
22+
*/
23+
class FlattenExceptionNormalizerTest extends TestCase
24+
{
25+
/**
26+
* @var FlattenExceptionNormalizer
27+
*/
28+
private $normalizer;
29+
30+
protected function setUp(): void
31+
{
32+
$this->normalizer = new FlattenExceptionNormalizer();
33+
}
34+
35+
public function testSupportsNormalization(): void
36+
{
37+
$this->assertTrue($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): void
45+
{
46+
$normalized = $this->normalizer->normalize($exception);
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+
$this->assertSame($exception->getStatusCode(), $normalized['status_code']);
52+
$this->assertSame($exception->getHeaders(), $normalized['headers']);
53+
$this->assertSame($exception->getClass(), $normalized['class']);
54+
$this->assertSame($exception->getFile(), $normalized['file']);
55+
$this->assertSame($exception->getLine(), $normalized['line']);
56+
$this->assertSame($previous, $normalized['previous']);
57+
$this->assertSame($exception->getTrace(), $normalized['trace']);
58+
$this->assertSame($exception->getTraceAsString(), $normalized['trace_as_string']);
59+
}
60+
61+
public function provideFlattenException(): array
62+
{
63+
return [
64+
'instance from constructor' => [new FlattenException()],
65+
'instance from exception' => [FlattenException::createFromThrowable(new \RuntimeException('foo', 42))],
66+
'instance with previous exception' => [FlattenException::createFromThrowable(new \RuntimeException('foo', 42, new \Exception()))],
67+
'instance with headers' => [FlattenException::createFromThrowable(new \RuntimeException('foo', 42), 404, ['Foo' => 'Bar'])],
68+
];
69+
}
70+
71+
public function testNormalizeBadObjectTypeThrowsException(): void
72+
{
73+
$this->expectException(InvalidArgumentException::class);
74+
$this->normalizer->normalize(new \stdClass());
75+
}
76+
77+
public function testSupportsDenormalization(): void
78+
{
79+
$this->assertTrue($this->normalizer->supportsDenormalization(null, FlattenException::class));
80+
$this->assertFalse($this->normalizer->supportsDenormalization(null, \stdClass::class));
81+
}
82+
83+
public function testDenormalizeValidData(): void
84+
{
85+
$normalized = [];
86+
$exception = $this->normalizer->denormalize($normalized, FlattenException::class);
87+
88+
$this->assertInstanceOf(FlattenException::class, $exception);
89+
$this->assertNull($exception->getMessage());
90+
$this->assertNull($exception->getCode());
91+
$this->assertNull($exception->getStatusCode());
92+
$this->assertNull($exception->getHeaders());
93+
$this->assertNull($exception->getClass());
94+
$this->assertNull($exception->getFile());
95+
$this->assertNull($exception->getLine());
96+
$this->assertNull($exception->getPrevious());
97+
$this->assertNull($exception->getTrace());
98+
$this->assertNull($exception->getTraceAsString());
99+
100+
$normalized = [
101+
'message' => 'Something went foobar.',
102+
'code' => 42,
103+
'status_code' => 404,
104+
'headers' => ['Content-Type' => 'application/json'],
105+
'class' => \get_class($this),
106+
'file' => 'foo.php',
107+
'line' => 123,
108+
'previous' => [
109+
'message' => 'Previous exception',
110+
'code' => 0,
111+
],
112+
'trace' => [
113+
[
114+
'namespace' => '', 'short_class' => '', 'class' => '', 'type' => '', 'function' => '', 'file' => 'foo.php', 'line' => 123, 'args' => [],
115+
],
116+
],
117+
'trace_as_string' => '#0 foo.php(123): foo()'.PHP_EOL.'#1 bar.php(456): bar()',
118+
];
119+
$exception = $this->normalizer->denormalize($normalized, FlattenException::class);
120+
121+
$this->assertInstanceOf(FlattenException::class, $exception);
122+
$this->assertSame($normalized['message'], $exception->getMessage());
123+
$this->assertSame($normalized['code'], $exception->getCode());
124+
$this->assertSame($normalized['status_code'], $exception->getStatusCode());
125+
$this->assertSame($normalized['headers'], $exception->getHeaders());
126+
$this->assertSame($normalized['class'], $exception->getClass());
127+
$this->assertSame($normalized['file'], $exception->getFile());
128+
$this->assertSame($normalized['line'], $exception->getLine());
129+
$this->assertSame($normalized['trace'], $exception->getTrace());
130+
$this->assertSame($normalized['trace_as_string'], $exception->getTraceAsString());
131+
132+
$this->assertInstanceOf(FlattenException::class, $previous = $exception->getPrevious());
133+
$this->assertSame($normalized['previous']['message'], $previous->getMessage());
134+
$this->assertSame($normalized['previous']['code'], $previous->getCode());
135+
}
136+
137+
/**
138+
* @dataProvider provideInvalidNormalizedData
139+
*/
140+
public function testDenormalizeInvalidDataThrowsException($normalized): void
141+
{
142+
$this->expectException(NotNormalizableValueException::class);
143+
$this->normalizer->denormalize($normalized, FlattenException::class);
144+
}
145+
146+
public function provideInvalidNormalizedData(): array
147+
{
148+
return [
149+
'null' => [null],
150+
'string' => ['foo'],
151+
'integer' => [42],
152+
'object' => [new \stdClass()],
153+
];
154+
}
155+
}

src/Symfony/Component/Serializer/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"symfony/property-access": "~3.4|~4.0",
2626
"symfony/http-foundation": "~3.4|~4.0",
2727
"symfony/cache": "~3.4|~4.0",
28+
"symfony/debug": "~3.4|~4.0",
2829
"symfony/property-info": "^3.4.13|~4.0",
2930
"symfony/validator": "~3.4|~4.0",
3031
"doctrine/annotations": "~1.0",

0 commit comments

Comments
 (0)