Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b65a425

Browse files
committed
[Serializer][PropertyInfo] Fix default groups
1 parent 155ac0d commit b65a425

File tree

14 files changed

+335
-63
lines changed

14 files changed

+335
-63
lines changed

src/Symfony/Component/PropertyInfo/Extractor/SerializerExtractor.php

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

1414
use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
1515
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
16+
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
1617

1718
/**
1819
* Lists available properties using Symfony Serializer Component metadata.
@@ -38,15 +39,31 @@ public function getProperties(string $class, array $context = []): ?array
3839
return null;
3940
}
4041

42+
$enableDefaultGroups = $context[AbstractNormalizer::ENABLE_DEFAULT_GROUPS] ?? false;
43+
4144
$groups = $context['serializer_groups'] ?? [];
42-
$groupsHasBeenDefined = [] !== $groups;
43-
$groups = array_merge($groups, ['Default', (false !== $nsSep = strrpos($class, '\\')) ? substr($class, $nsSep + 1) : $class]);
45+
$defaultGroups = ['Default', (false !== $nsSep = strrpos($class, '\\')) ? substr($class, $nsSep + 1) : $class];
46+
47+
$groupsHasBeenDefined = null !== ($context['serializer_groups'] ?? null);
48+
$customGroupsHasBeenDefined = (bool) array_diff($groups, $defaultGroups);
49+
50+
if ($enableDefaultGroups && !$customGroupsHasBeenDefined) {
51+
$groups = array_merge($groups, $defaultGroups);
52+
}
4453

4554
$properties = [];
4655
$serializerClassMetadata = $this->classMetadataFactory->getMetadataFor($class);
4756

4857
foreach ($serializerClassMetadata->getAttributesMetadata() as $serializerAttributeMetadata) {
49-
if (!$serializerAttributeMetadata->isIgnored() && (!$groupsHasBeenDefined || array_intersect(array_merge($serializerAttributeMetadata->getGroups(), ['*']), $groups))) {
58+
if ($serializerAttributeMetadata->isIgnored()) {
59+
continue;
60+
}
61+
62+
if (!($attributeGroups = $serializerAttributeMetadata->getGroups()) && $enableDefaultGroups && !$customGroupsHasBeenDefined) {
63+
$attributeGroups = $defaultGroups;
64+
}
65+
66+
if (!$groupsHasBeenDefined || array_intersect(array_merge($attributeGroups, ['*']), $groups)) {
5067
$properties[] = $serializerAttributeMetadata->getName();
5168
}
5269
}

src/Symfony/Component/PropertyInfo/Tests/Extractor/SerializerExtractorTest.php

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\PropertyInfo\Extractor\SerializerExtractor;
1616
use Symfony\Component\PropertyInfo\Tests\Fixtures\AdderRemoverDummy;
1717
use Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy;
18+
use Symfony\Component\PropertyInfo\Tests\Fixtures\GroupPropertyDummy;
1819
use Symfony\Component\PropertyInfo\Tests\Fixtures\IgnorePropertyDummy;
1920
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
2021
use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
@@ -43,12 +44,90 @@ public function testGetProperties()
4344
public function testGetPropertiesWithIgnoredProperties()
4445
{
4546
$this->assertSame(['visibleProperty'], $this->extractor->getProperties(IgnorePropertyDummy::class, ['serializer_groups' => ['a']]));
46-
$this->assertSame(['visibleProperty'], $this->extractor->getProperties(IgnorePropertyDummy::class, ['serializer_groups' => ['Default']]));
47-
$this->assertSame(['visibleProperty'], $this->extractor->getProperties(IgnorePropertyDummy::class, ['serializer_groups' => ['IgnorePropertyDummy']]));
4847
}
4948

5049
public function testGetPropertiesWithAnyGroup()
5150
{
5251
$this->assertSame(['analyses', 'feet'], $this->extractor->getProperties(AdderRemoverDummy::class, ['serializer_groups' => null]));
5352
}
53+
54+
public function testGetPropertiesWithDefaultGroups()
55+
{
56+
$this->assertSame(
57+
['noGroup', 'customGroup', 'defaultGroup', 'classGroup'],
58+
$this->extractor->getProperties(GroupPropertyDummy::class),
59+
);
60+
61+
$this->assertSame(
62+
['noGroup'],
63+
$this->extractor->getProperties(GroupPropertyDummy::class, ['serializer_groups' => []]),
64+
);
65+
66+
$this->assertSame(
67+
['noGroup', 'customGroup', 'defaultGroup', 'classGroup'],
68+
$this->extractor->getProperties(GroupPropertyDummy::class, ['serializer_groups' => ['*']]),
69+
);
70+
71+
$this->assertSame(
72+
['customGroup'],
73+
$this->extractor->getProperties(GroupPropertyDummy::class, ['serializer_groups' => ['custom']]),
74+
);
75+
76+
$this->assertSame(
77+
['defaultGroup'],
78+
$this->extractor->getProperties(GroupPropertyDummy::class, ['serializer_groups' => ['Default']]),
79+
);
80+
81+
$this->assertSame(
82+
['classGroup'],
83+
$this->extractor->getProperties(GroupPropertyDummy::class, ['serializer_groups' => ['GroupPropertyDummy']]),
84+
);
85+
86+
$this->assertSame(
87+
['noGroup', 'customGroup', 'defaultGroup', 'classGroup'],
88+
$this->extractor->getProperties(GroupPropertyDummy::class, [
89+
'enable_default_groups' => true,
90+
]),
91+
);
92+
93+
$this->assertSame(
94+
['noGroup', 'customGroup', 'defaultGroup', 'classGroup'],
95+
$this->extractor->getProperties(GroupPropertyDummy::class, [
96+
'enable_default_groups' => true,
97+
'serializer_groups' => [],
98+
]),
99+
);
100+
101+
$this->assertSame(
102+
['noGroup', 'customGroup', 'defaultGroup', 'classGroup'],
103+
$this->extractor->getProperties(GroupPropertyDummy::class, [
104+
'enable_default_groups' => true,
105+
'serializer_groups' => ['*'],
106+
]),
107+
);
108+
109+
$this->assertSame(
110+
['customGroup'],
111+
$this->extractor->getProperties(GroupPropertyDummy::class, [
112+
'enable_default_groups' => true,
113+
'serializer_groups' => ['custom'],
114+
]),
115+
);
116+
117+
$this->assertSame(
118+
['noGroup', 'defaultGroup', 'classGroup'],
119+
$this->extractor->getProperties(GroupPropertyDummy::class, [
120+
'enable_default_groups' => true,
121+
'serializer_groups' => ['Default'],
122+
]),
123+
);
124+
125+
$this->assertSame(
126+
['noGroup', 'defaultGroup', 'classGroup'],
127+
$this->extractor->getProperties(GroupPropertyDummy::class, [
128+
'enable_default_groups' => true,
129+
'serializer_groups' => ['GroupPropertyDummy'],
130+
]),
131+
);
132+
}
54133
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\PropertyInfo\Tests\Fixtures;
13+
14+
use Symfony\Component\Serializer\Attribute\Groups;
15+
16+
class GroupPropertyDummy
17+
{
18+
public bool $noGroup = true;
19+
20+
#[Groups('custom')]
21+
public bool $customGroup = true;
22+
23+
#[Groups('Default')]
24+
public bool $defaultGroup = true;
25+
26+
#[Groups('GroupPropertyDummy')]
27+
public bool $classGroup = true;
28+
}

src/Symfony/Component/PropertyInfo/Tests/Fixtures/IgnorePropertyDummy.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
*/
2020
class IgnorePropertyDummy
2121
{
22-
#[Groups(['a', 'Default', 'IgnorePropertyDummy'])]
22+
#[Groups(['a'])]
2323
public $visibleProperty;
2424

2525
#[Groups(['a']), Ignore]

src/Symfony/Component/Serializer/Context/Normalizer/AbstractObjectNormalizerContextBuilder.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,4 +128,13 @@ public function withPreserveEmptyObjects(?bool $preserveEmptyObjects): static
128128
{
129129
return $this->with(AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS, $preserveEmptyObjects);
130130
}
131+
132+
/**
133+
* Configures whether 'Default' and 'ClassNames' groups are added when no
134+
* custom group is specified.
135+
*/
136+
public function withEnableDefaultGroups(?bool $enableDefaultGroups): static
137+
{
138+
return $this->with(AbstractObjectNormalizer::ENABLE_DEFAULT_GROUPS, $enableDefaultGroups);
139+
}
131140
}

src/Symfony/Component/Serializer/NameConverter/MetadataAwareNameConverter.php

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,13 @@ public function normalize(string $propertyName, ?string $class = null, ?string $
4747
return $this->normalizeFallback($propertyName, $class, $format, $context);
4848
}
4949

50-
if (!\array_key_exists($class, self::$normalizeCache) || !\array_key_exists($propertyName, self::$normalizeCache[$class])) {
51-
self::$normalizeCache[$class][$propertyName] = $this->getCacheValueForNormalization($propertyName, $class);
50+
$cacheKey = $this->getCacheKey($class, $context);
51+
52+
if (!\array_key_exists($cacheKey, self::$normalizeCache) || !\array_key_exists($propertyName, self::$normalizeCache[$cacheKey])) {
53+
self::$normalizeCache[$cacheKey][$propertyName] = $this->getCacheValueForNormalization($propertyName, $class, $context);
5254
}
5355

54-
return self::$normalizeCache[$class][$propertyName] ?? $this->normalizeFallback($propertyName, $class, $format, $context);
56+
return self::$normalizeCache[$cacheKey][$propertyName] ?? $this->normalizeFallback($propertyName, $class, $format, $context);
5557
}
5658

5759
public function denormalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string
@@ -61,29 +63,23 @@ public function denormalize(string $propertyName, ?string $class = null, ?string
6163
}
6264

6365
$cacheKey = $this->getCacheKey($class, $context);
66+
6467
if (!\array_key_exists($cacheKey, self::$denormalizeCache) || !\array_key_exists($propertyName, self::$denormalizeCache[$cacheKey])) {
6568
self::$denormalizeCache[$cacheKey][$propertyName] = $this->getCacheValueForDenormalization($propertyName, $class, $context);
6669
}
6770

6871
return self::$denormalizeCache[$cacheKey][$propertyName] ?? $this->denormalizeFallback($propertyName, $class, $format, $context);
6972
}
7073

71-
private function getCacheValueForNormalization(string $propertyName, string $class): ?string
74+
private function getCacheValueForNormalization(string $propertyName, string $class, array $context): ?string
7275
{
73-
if (!$this->metadataFactory->hasMetadataFor($class)) {
74-
return null;
75-
}
76-
77-
$attributesMetadata = $this->metadataFactory->getMetadataFor($class)->getAttributesMetadata();
78-
if (!\array_key_exists($propertyName, $attributesMetadata)) {
79-
return null;
80-
}
76+
$cacheKey = $this->getCacheKey($class, $context);
8177

82-
if (null !== $attributesMetadata[$propertyName]->getSerializedName() && null !== $attributesMetadata[$propertyName]->getSerializedPath()) {
83-
throw new LogicException(sprintf('Found SerializedName and SerializedPath attributes on property "%s" of class "%s".', $propertyName, $class));
78+
if (!\array_key_exists($cacheKey, self::$attributesMetadataCache)) {
79+
self::$attributesMetadataCache[$cacheKey] = $this->getCacheValueForAttributesMetadata($class, $context);
8480
}
8581

86-
return $attributesMetadata[$propertyName]->getSerializedName() ?? null;
82+
return array_flip(self::$attributesMetadataCache[$cacheKey])[$propertyName] ?? null;
8783
}
8884

8985
private function normalizeFallback(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string
@@ -117,6 +113,18 @@ private function getCacheValueForAttributesMetadata(string $class, array $contex
117113

118114
$classMetadata = $this->metadataFactory->getMetadataFor($class);
119115

116+
$enableDefaultGroups = $context[AbstractNormalizer::ENABLE_DEFAULT_GROUPS] ?? false;
117+
118+
$groups = (array) ($context[AbstractNormalizer::GROUPS] ?? []);
119+
$defaultGroups = ['Default', (false !== $nsSep = strrpos($class, '\\')) ? substr($class, $nsSep + 1) : $class];
120+
121+
$groupsHasBeenDefined = [] !== $groups;
122+
$customGroupsHasBeenDefined = (bool) array_diff($groups, $defaultGroups);
123+
124+
if ($enableDefaultGroups && !$customGroupsHasBeenDefined) {
125+
$groups = array_merge($groups, $defaultGroups);
126+
}
127+
120128
$cache = [];
121129
foreach ($classMetadata->getAttributesMetadata() as $name => $metadata) {
122130
if (null === $metadata->getSerializedName()) {
@@ -129,15 +137,11 @@ private function getCacheValueForAttributesMetadata(string $class, array $contex
129137

130138
$metadataGroups = $metadata->getGroups();
131139

132-
$contextGroups = (array) ($context[AbstractNormalizer::GROUPS] ?? []);
133-
$contextGroupsHasBeenDefined = [] !== $contextGroups;
134-
$contextGroups = array_merge($contextGroups, ['Default', (false !== $nsSep = strrpos($class, '\\')) ? substr($class, $nsSep + 1) : $class]);
135-
136-
if ($contextGroupsHasBeenDefined && !$metadataGroups) {
140+
if ($metadataGroups && !array_intersect(array_merge($metadataGroups, ['*']), $groups)) {
137141
continue;
138142
}
139143

140-
if ($metadataGroups && !array_intersect(array_merge($metadataGroups, ['*']), $contextGroups)) {
144+
if (!$metadataGroups && $groupsHasBeenDefined && !\in_array('*', $groups)) {
141145
continue;
142146
}
143147

@@ -153,6 +157,6 @@ private function getCacheKey(string $class, array $context): string
153157
return $class.'-'.$context['cache_key'];
154158
}
155159

156-
return $class.hash('xxh128', serialize($context[AbstractNormalizer::GROUPS] ?? []));
160+
return $class.hash('xxh128', serialize($context[AbstractNormalizer::GROUPS] ?? []).serialize($context[AbstractNormalizer::ENABLE_DEFAULT_GROUPS] ?? false));
157161
}
158162
}

src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,11 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn
128128
*/
129129
public const FILTER_BOOL = 'filter_bool';
130130

131+
/**
132+
* Add 'Default' and 'ClassNames' groups when no custom group is specified.
133+
*/
134+
public const ENABLE_DEFAULT_GROUPS = 'enable_default_groups';
135+
131136
/**
132137
* @internal
133138
*/
@@ -226,9 +231,17 @@ protected function getAllowedAttributes(string|object $classOrObject, array $con
226231
$classMetadata = $this->classMetadataFactory->getMetadataFor($classOrObject);
227232
$class = $classMetadata->getName();
228233

234+
$enableDefaultGroups = $context[self::ENABLE_DEFAULT_GROUPS] ?? $this->defaultContext[self::ENABLE_DEFAULT_GROUPS] ?? false;
235+
229236
$groups = $this->getGroups($context);
237+
$defaultGroups = ['Default', (false !== $nsSep = strrpos($class, '\\')) ? substr($class, $nsSep + 1) : $class];
238+
230239
$groupsHasBeenDefined = [] !== $groups;
231-
$groups = array_merge($groups, ['Default', (false !== $nsSep = strrpos($class, '\\')) ? substr($class, $nsSep + 1) : $class]);
240+
$customGroupsHasBeenDefined = (bool) array_diff($groups, $defaultGroups);
241+
242+
if ($enableDefaultGroups && !$customGroupsHasBeenDefined) {
243+
$groups = array_merge($groups, $defaultGroups);
244+
}
232245

233246
$allowedAttributes = [];
234247
$ignoreUsed = false;
@@ -238,12 +251,16 @@ protected function getAllowedAttributes(string|object $classOrObject, array $con
238251
$ignoreUsed = true;
239252
}
240253

241-
// If you update this check, update accordingly the one in Symfony\Component\PropertyInfo\Extractor\SerializerExtractor::getProperties()
242-
if (
243-
!$ignore
244-
&& (!$groupsHasBeenDefined || array_intersect(array_merge($attributeMetadata->getGroups(), ['*']), $groups))
245-
&& $this->isAllowedAttribute($classOrObject, $name = $attributeMetadata->getName(), null, $context)
246-
) {
254+
// If you update these checks, update accordingly the one in Symfony\Component\PropertyInfo\Extractor\SerializerExtractor::getProperties()
255+
if ($ignore || !$this->isAllowedAttribute($classOrObject, $name = $attributeMetadata->getName(), null, $context)) {
256+
continue;
257+
}
258+
259+
if (!($attributeGroups = $attributeMetadata->getGroups()) && $enableDefaultGroups && !$customGroupsHasBeenDefined) {
260+
$attributeGroups = $defaultGroups;
261+
}
262+
263+
if (!$groupsHasBeenDefined || array_intersect(array_merge($attributeGroups, ['*']), $groups)) {
247264
$allowedAttributes[] = $attributesAsString ? $name : $attributeMetadata;
248265
}
249266
}

src/Symfony/Component/Serializer/Tests/Context/Normalizer/AbstractObjectNormalizerContextBuilderTest.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public function testWithers(array $values)
4545
->withExcludeFromCacheKeys($values[AbstractObjectNormalizer::EXCLUDE_FROM_CACHE_KEY])
4646
->withDeepObjectToPopulate($values[AbstractObjectNormalizer::DEEP_OBJECT_TO_POPULATE])
4747
->withPreserveEmptyObjects($values[AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS])
48+
->withEnableDefaultGroups($values[AbstractObjectNormalizer::ENABLE_DEFAULT_GROUPS])
4849
->toArray();
4950

5051
$this->assertSame($values, $context);
@@ -65,6 +66,7 @@ public static function withersDataProvider(): iterable
6566
AbstractObjectNormalizer::EXCLUDE_FROM_CACHE_KEY => ['key'],
6667
AbstractObjectNormalizer::DEEP_OBJECT_TO_POPULATE => true,
6768
AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS => false,
69+
AbstractObjectNormalizer::ENABLE_DEFAULT_GROUPS => false,
6870
]];
6971

7072
yield 'With null values' => [[
@@ -77,6 +79,7 @@ public static function withersDataProvider(): iterable
7779
AbstractObjectNormalizer::EXCLUDE_FROM_CACHE_KEY => null,
7880
AbstractObjectNormalizer::DEEP_OBJECT_TO_POPULATE => null,
7981
AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS => null,
82+
AbstractObjectNormalizer::ENABLE_DEFAULT_GROUPS => null,
8083
]];
8184
}
8285

src/Symfony/Component/Serializer/Tests/Fixtures/OtherSerializedNameDummy.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,18 @@
1919
*/
2020
class OtherSerializedNameDummy
2121
{
22+
#[Groups(['Default']), SerializedName('renamedDefaultGroup')]
23+
private $defaultGroup;
24+
25+
#[Groups(['OtherSerializedNameDummy']), SerializedName('renamedClassGroup')]
26+
public $classGroup;
27+
28+
#[SerializedName('renamedNoGroup')]
29+
public $noGroup;
30+
31+
#[Groups(['custom']), SerializedName('renamedCustomGroup')]
32+
public $customGroup;
33+
2234
#[Groups(['a'])]
2335
private $buz;
2436

0 commit comments

Comments
 (0)