8000 UnwrappingDenormalizer · symfony/symfony@00d103d · GitHub
[go: up one dir, main page]

Skip to content

Commit 00d103d

Browse files
committed
UnwrappingDenormalizer
inject an existing instance of PropertyAccess, implement hasCacheableSupportsMethod Coding Standard fix resolve conversations test denormalizer
1 parent 37a8863 commit 00d103d

File tree

5 files changed

+179
-0
lines changed

5 files changed

+179
-0
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@
109109
use Symfony\Component\Serializer\Encoder\EncoderInterface;
110110
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
111111
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
112+
use Symfony\Component\Serializer\Normalizer\UnwrappingDenormalizer;
112113
use Symfony\Component\Stopwatch\Stopwatch;
113114
use Symfony\Component\String\Slugger\SluggerInterface;
114115
use Symfony\Component\Translation\Command\XliffLintCommand as BaseXliffLintCommand;
@@ -1412,6 +1413,10 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder
14121413
$container->removeDefinition('serializer.encoder.yaml');
14131414
}
14141415

1416+
if (!class_exists(UnwrappingDenormalizer::class)) {
1417+
$container->removeDefinition('serializer.denormalizer.unwrapping');
1418+
}
1419+
14151420
$serializerLoaders = [];
14161421
if (isset($config['enable_annotations']) && $config['enable_annotations']) {
14171422
if (!$this->annotationsConfigEnabled) {

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,12 @@
7070
<tag name="serializer.normalizer" priority="-890" />
7171
</service>
7272

73+
<service id="serializer.denormalizer.unwrapping" class="Symfony\Component\Serializer\Normalizer\UnwrappingDenormalizer">
74+
<argument type="service" id="serializer.property_accessor" />
75+
<!-- Run before serializer.normalizer.object -->
76+
<tag name="serializer.normalizer" priority="1000" />
77+
</service>
78+
7379
<service id="serializer.normalizer.object" class="Symfony\Component\Serializer\Normalizer\ObjectNormalizer">
7480
<argument type="service" id="serializer.mapping.class_metadata_factory" />
7581
<argument type="service" id="serializer.name_converter.metadata_aware" />
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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\PropertyAccess\PropertyAccess;
15+
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
16+
use Symfony\Component\Serializer\SerializerAwareInterface;
17+
use Symfony\Component\Serializer\SerializerAwareTrait;
18+
19+
/**
20+
* @author Eduard Bulava <bulavaeduard@gmail.com>
21+
*/
22+
final class UnwrappingDenormalizer implements DenormalizerInterface, SerializerAwareInterface, CacheableSupportsMethodInterface
23+
{
24+
use SerializerAwareTrait;
25+
26+
const UNWRAP_PATH = 'unwrap_path';
27+
28+
private $propertyAccessor;
29+
30+
public function __construct(PropertyAccessorInterface $propertyAccessor = null)
31+
{
32+
$this->propertyAccessor = $propertyAccessor ?? PropertyAccess::createPropertyAccessor();
33+
}
34+
35+
/**
36+
* {@inheritdoc}
37+
*/
38+
public function denormalize($data, $class, string $format = null, array $context = [])
39+
{
40+
$propertyPath = $context[self::UNWRAP_PATH];
41+
$context['unwrapped'] = true;
42+
43+
if ($propertyPath) {
44+
if (!$this->propertyAccessor->isReadable($data, $propertyPath)) {
45+
return null;
46+
}
47+
48+
$data = $this->propertyAccessor->getValue($data, $propertyPath);
49+
}
50+
51+
return $this->serializer->denormalize($data, $class, $format, $context);
52+
}
53+
54+
/**
55+
* {@inheritdoc}
56+
*/
57+
public function supportsDenormalization($data, $type, string $format = null, array $context = [])
58+
{
59+
return \array_key_exists(self::UNWRAP_PATH, $context) && !isset($context['unwrapped']);
60+
}
61+
62+
/**
63+
* {@inheritdoc}
64+
*/
65+
public function hasCacheableSupportsMethod(): bool
66+
{
67+
return $this->serializer instanceof CacheableSupportsMethodInterface && $this->serializer->hasCacheableSupportsMethod();
68+
}
69+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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\Serializer\Normalizer\UnwrappingDenormalizer;
16+
use Symfony\Component\Serializer\Tests\Normalizer\Features\ObjectDummy;
17+
18+
/**
19+
* @author Eduard Bulava <bulavaeduard@gmail.com>
20+
*/
21+
class UnwrappinDenormalizerTest extends TestCase
22+
{
23+
private $denormalizer;
24+
25+
private $serializer;
26+
27+
protected function setUp(): void
28+
{
29+
$this->serializer = $this->getMockBuilder('Symfony\Component\Serializer\Serializer')->getMock();
30+
$this->denormalizer = new UnwrappingDenormalizer();
31+
$this->denormalizer->setSerializer($this->serializer);
32+
}
33+
34+
public function testSupportsNormalization()
35+
{
36+
$this->assertTrue($this->denormalizer->supportsDenormalization([], new \stdClass(), 'any', [UnwrappingDenormalizer::UNWRAP_PATH => '[baz][inner]']));
37+
$this->assertFalse($this->denormalizer->supportsDenormalization([], new \stdClass(), 'any', [UnwrappingDenormalizer::UNWRAP_PATH => '[baz][inner]', 'unwrapped' => true]));
38+
$this->assertFalse($this->denormalizer->supportsDenormalization([], new \stdClass(), 'any', []));
39+
}
40+
41+
public function testDenormalize()
42+
{
43+
$expected = new ObjectDummy();
44+
$expected->setBaz(true);
45+
$expected->bar = 'bar';
46+
$expected->setFoo('foo');
47+
48+
$this->serializer->expects($this->exactly(1))
49+
->method('denormalize')
50+
->with(['foo' => 'foo', 'bar' => 'bar', 'baz' => true])
51+
->willReturn($expected);
52+
53+
$result = $this->denormalizer->denormalize(
54+
['data' => ['foo' => 'foo', 'bar' => 'bar', 'baz' => true]],
55+
ObjectDummy::class,
56+
'any',
57+
[UnwrappingDenormalizer::UNWRAP_PATH => '[data]']
58+
);
59+
60+
$this->assertEquals('foo', $result->getFoo());
61+
$this->assertEquals('bar', $result->bar);
62+
$this->assertTrue($result->isBaz());
63+
}
64+
65+
public function testDenormalizeInvalidPath()
66+
{
67+
$this->serializer->expects($this->exactly(1))
68+
->method('denormalize')
69+
->with(null)
70+
->willReturn(new ObjectDummy());
71+
72+
$obj = $this->denormalizer->denormalize(
73+
['data' => ['foo' => 'foo', 'bar' => 'bar', 'baz' => true]],
74+
ObjectDummy::class,
75+
'any',
76+
[UnwrappingDenormalizer::UNWRAP_PATH => '[invalid]']
77+
);
78+
79+
$this->assertNull($obj->getFoo());
80+
$this->assertNull($obj->bar);
81+
$this->assertNull($obj->isBaz());
82+
}
83+
}

src/Symfony/Component/Serializer/Tests/SerializerTest.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Doctrine\Common\Annotations\AnnotationReader;
1515
use PHPUnit\Framework\TestCase;
16+
use Symfony\Component\PropertyAccess\PropertyAccessor;
1617
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
1718
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
1819
use Symfony\Component\Serializer\Encoder\JsonEncoder;
@@ -34,6 +35,7 @@
3435
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
3536
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
3637
use Symfony\Component\Serializer\Normalizer\PropertyNormalizer;
38+
use Symfony\Component\Serializer\Normalizer\UnwrappingDenormalizer;
3739
use Symfony\Component\Serializer\Serializer;
3840
use Symfony\Component\Serializer\Tests\Fixtures\AbstractDummy;
3941
use Symfony\Component\Serializer\Tests\Fixtures\AbstractDummyFirstChild;
@@ -494,6 +496,20 @@ private function serializerWithClassDiscriminator()
494496

495497
return new Serializer([new ObjectNormalizer($classMetadataFactory, null, null, new ReflectionExtractor(), new ClassDiscriminatorFromClassMetadata($classMetadataFactory))], ['json' => new JsonEncoder()]);
496498
}
499+
500+
public function testDeserializeAndUnwrap()
501+
{
502+
$jsonData = '{"baz": {"foo": "bar", "inner": {"title": "value", "numbers": [5,3]}}}';
503+
504+
$expectedData = Model::fromArray(['title' => 'value', 'numbers' => [5, 3]]);
505+
506+
$serializer = new Serializer([new UnwrappingDenormalizer(new PropertyAccessor()), new ObjectNormalizer()], ['json' => new JsonEncoder()]);
507+
508+
$this->assertEquals(
509+
$expectedData,
510+
$serializer->deserialize($jsonData, __NAMESPACE__.'\Model', 'json', [UnwrappingDenormalizer::UNWRAP_PATH => '[baz][inner]'])
511+
);
512+
}
497513
}
498514

499515
class Model

0 commit comments

Comments
 (0)
0