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

Skip to content

[PropertyInfo][Serializer] Fix default groups #58656

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;

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

$enableDefaultGroups = $context[AbstractNormalizer::ENABLE_DEFAULT_GROUPS] ?? false;

$groups = $context['serializer_groups'] ?? [];
$groupsHasBeenDefined = [] !== $groups;
$groups = array_merge($groups, ['Default', (false !== $nsSep = strrpos($class, '\\')) ? substr($class, $nsSep + 1) : $class]);
$defaultGroups = ['Default', (false !== $nsSep = strrpos($class, '\\')) ? substr($class, $nsSep + 1) : $class];

$groupsHasBeenDefined = $enableDefaultGroups
? [] !== $groups
: null !== ($context['serializer_groups'] ?? null);

$customGroupsHasBeenDefined = (bool) array_diff($groups, $defaultGroups);

if ($enableDefaultGroups && !$customGroupsHasBeenDefined) {
$groups = array_merge($groups, $defaultGroups);
}

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

foreach ($serializerClassMetadata->getAttributesMetadata() as $serializerAttributeMetadata) {
if (!$serializerAttributeMetadata->isIgnored() && (!$groupsHasBeenDefined || array_intersect(array_merge($serializerAttributeMetadata->getGroups(), ['*']), $groups))) {
if ($serializerAttributeMetadata->isIgnored()) {
continue;
}

if (!($attributeGroups = $serializerAttributeMetadata->getGroups()) && $enableDefaultGroups && !$customGroupsHasBeenDefined) {
$attributeGroups = $defaultGroups;
}

if (!$groupsHasBeenDefined || array_intersect(array_merge($attributeGroups, ['*']), $groups)) {
$properties[] = $serializerAttributeMetadata->getName();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Symfony\Component\PropertyInfo\Extractor\SerializerExtractor;
use Symfony\Component\PropertyInfo\Tests\Fixtures\AdderRemoverDummy;
use Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy;
use Symfony\Component\PropertyInfo\Tests\Fixtures\GroupPropertyDummy;
use Symfony\Component\PropertyInfo\Tests\Fixtures\IgnorePropertyDummy;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
Expand Down Expand Up @@ -43,12 +44,91 @@ public function testGetProperties()
public function testGetPropertiesWithIgnoredProperties()
{
$this->assertSame(['visibleProperty'], $this->extractor->getProperties(IgnorePropertyDummy::class, ['serializer_groups' => ['a']]));
$this->assertSame(['visibleProperty'], $this->extractor->getProperties(IgnorePropertyDummy::class, ['serializer_groups' => ['Default']]));
$this->assertSame(['visibleProperty'], $this->extractor->getProperties(IgnorePropertyDummy::class, ['serializer_groups' => ['IgnorePropertyDummy']]));
}

public function testGetPropertiesWithAnyGroup()
{
$this->assertSame(['analyses', 'feet'], $this->extractor->getProperties(AdderRemoverDummy::class, ['serializer_groups' => null]));
}

public function testGetPropertiesWithDefaultGroups()
{
$this->assertSame(
['noGroup', 'customGroup', 'defaultGroup', 'classGroup'],
$this->extractor->getProperties(GroupPropertyDummy::class, ['serializer_groups' => null]),
);

$this->assertSame(
[],
$this->extractor->getProperties(GroupPropertyDummy::class, ['serializer_groups' => []]),
);

$this->assertSame(
['noGroup', 'customGroup', 'defaultGroup', 'classGroup'],
$this->extractor->getProperties(GroupPropertyDummy::class, ['serializer_groups' => ['*']]),
);

$this->assertSame(
['customGroup'],
$this->extractor->getProperties(GroupPropertyDummy::class, ['serializer_groups' => ['custom']]),
);

$this->assertSame(
['defaultGroup'],
$this->extractor->getProperties(GroupPropertyDummy::class, ['serializer_groups' => ['Default']]),
);

$this->assertSame(
['classGroup'],
$this->extractor->getProperties(GroupPropertyDummy::class, ['serializer_groups' => ['GroupPropertyDummy']]),
);

$this->assertSame(
['noGroup', 'customGroup', 'defaultGroup', 'classGroup'],
$this->extractor->getProperties(GroupPropertyDummy::class, [
'enable_default_groups' => true,
'serializer_groups' => null,
]),
);

$this->assertSame(
['noGroup', 'customGroup', 'defaultGroup', 'classGroup'],
$this->extractor->getProperties(GroupPropertyDummy::class, [
'enable_default_groups' => true,
'serializer_groups' => [],
]),
);

$this->assertSame(
['noGroup', 'customGroup', 'defaultGroup', 'classGroup'],
$this->extractor->getProperties(GroupPropertyDummy::class, [
'enable_default_groups' => true,
'serializer_groups' => ['*'],
]),
);

$this->assertSame(
['customGroup'],
$this->extractor->getProperties(GroupPropertyDummy::class, [
'enable_default_groups' => true,
'serializer_groups' => ['custom'],
]),
);

$this->assertSame(
['noGroup', 'defaultGroup', 'classGroup'],
$this->extractor->getProperties(GroupPropertyDummy::class, [
'enable_default_groups' => true,
'serializer_groups' => ['Default'],
]),
);

$this->assertSame(
['noGroup', 'defaultGroup', 'classGroup'],
$this->extractor->getProperties(GroupPropertyDummy::class, [
'enable_default_groups' => true,
'serializer_groups' => ['GroupPropertyDummy'],
]),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\PropertyInfo\Tests\Fixtures;

use Symfony\Component\Serializer\Attribute\Groups;

class GroupPropertyDummy
{
public bool $noGroup = true;

#[Groups('custom')]
public bool $customGroup = true;

#[Groups('Default')]
public bool $defaultGroup = true;

#[Groups('GroupPropertyDummy')]
public bool $classGroup = true;
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
*/
class IgnorePropertyDummy
{
#[Groups(['a', 'Default', 'IgnorePropertyDummy'])]
#[Groups(['a'])]
public $visibleProperty;

#[Groups(['a']), Ignore]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,13 @@ public function withPreserveEmptyObjects(?bool $preserveEmptyObjects): static
{
return $this->with(AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS, $preserveEmptyObjects);
}

/**
* Configures whether 'Default' and 'ClassNames' groups are added when no
* custom group is specified.
*/
public function withEnableDefaultGroups(?bool $enableDefaultGroups): static
{
return $this->with(AbstractObjectNormalizer::ENABLE_DEFAULT_GROUPS, $enableDefaultGroups);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,13 @@ public function normalize(string $propertyName, ?string $class = null, ?string $
return $this->normalizeFallback($propertyName, $class, $format, $context);
}

if (!\array_key_exists($class, self::$normalizeCache) || !\array_key_exists($propertyName, self::$normalizeCache[$class])) {
self::$normalizeCache[$class][$propertyName] = $this->getCacheValueForNormalization($propertyName, $class);
$cacheKey = $this->getCacheKey($class, $context);

if (!\array_key_exists($cacheKey, self::$normalizeCache) || !\array_key_exists($propertyName, self::$normalizeCache[$cacheKey])) {
self::$normalizeCache[$cacheKey][$propertyName] = $this->getCacheValueForNormalization($propertyName, $class, $context);
}

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

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

$cacheKey = $this->getCacheKey($class, $context);

if (!\array_key_exists($cacheKey, self::$denormalizeCache) || !\array_key_exists($propertyName, self::$denormalizeCache[$cacheKey])) {
self::$denormalizeCache[$cacheKey][$propertyName] = $this->getCacheValueForDenormalization($propertyName, $class, $context);
}

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

private function getCacheValueForNormalization(string $propertyName, string $class): ?string
private function getCacheValueForNormalization(string $propertyName, string $class, array $context): ?string
{
if (!$this->metadataFactory->hasMetadataFor($class)) {
return null;
}

$attributesMetadata = $this->metadataFactory->getMetadataFor($class)->getAttributesMetadata();
if (!\array_key_exists($propertyName, $attributesMetadata)) {
return null;
}
$cacheKey = $this->getCacheKey($class, $context);

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

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

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

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

$enableDefaultGroups = $context[AbstractNormalizer::ENABLE_DEFAULT_GROUPS] ?? false;

$groups = (array) ($context[AbstractNormalizer::GROUPS] ?? []);
$defaultGroups = ['Default', (false !== $nsSep = strrpos($class, '\\')) ? substr($class, $nsSep + 1) : $class];

$groupsHasBeenDefined = [] !== $groups;
$customGroupsHasBeenDefined = (bool) array_diff($groups, $defaultGroups);

if ($enableDefaultGroups && !$customGroupsHasBeenDefined) {
$groups = array_merge($groups, $defaultGroups);
}

$cache = [];
foreach ($classMetadata->getAttributesMetadata() as $name => $metadata) {
if (null === $metadata->getSerializedName()) {
Expand All @@ -129,15 +137,11 @@ private function getCacheValueForAttributesMetadata(string $class, array $contex

$metadataGroups = $metadata->getGroups();

$contextGroups = (array) ($context[AbstractNormalizer::GROUPS] ?? []);
$contextGroupsHasBeenDefined = [] !== $contextGroups;
$contextGroups = array_merge($contextGroups, ['Default', (false !== $nsSep = strrpos($class, '\\')) ? substr($class, $nsSep + 1) : $class]);

if ($contextGroupsHasBeenDefined && !$metadataGroups) {
if ($metadataGroups && !array_intersect(array_merge($metadataGroups, ['*']), $groups)) {
continue;
}

if ($metadataGroups && !array_intersect(array_merge($metadataGroups, ['*']), $contextGroups)) {
if (!$metadataGroups && $groupsHasBeenDefined && !\in_array('*', $groups)) {
continue;
}

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

return $class.hash('xxh128', serialize($context[AbstractNormalizer::GROUPS] ?? []));
return $class.hash('xxh128', serialize($context[AbstractNormalizer::GROUPS] ?? []).serialize($context[AbstractNormalizer::ENABLE_DEFAULT_GROUPS] ?? false));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn
*/
public const FILTER_BOOL = 'filter_bool';

/**
* Add 'Default' and 'ClassNames' groups when no custom group is specified.
*/
public const ENABLE_DEFAULT_GROUPS = 'enable_default_groups';

/**
* @internal
*/
Expand Down Expand Up @@ -226,9 +231,17 @@ protected function getAllowedAttributes(string|object $classOrObject, array $con
$classMetadata = $this->classMetadataFactory->getMetadataFor($classOrObject);
$class = $classMetadata->getName();

$enableDefaultGroups = $context[self::ENABLE_DEFAULT_GROUPS] ?? $this->defaultContext[self::ENABLE_DEFAULT_GROUPS] ?? false;

$groups = $this->getGroups($context);
$defaultGroups = ['Default', (false !== $nsSep = strrpos($class, '\\')) ? substr($class, $nsSep + 1) : $class];

$groupsHasBeenDefined = [] !== $groups;
$groups = array_merge($groups, ['Default', (false !== $nsSep = strrpos($class, '\\')) ? substr($class, $nsSep + 1) : $class]);
$customGroupsHasBeenDefined = (bool) array_diff($groups, $defaultGroups);

if ($enableDefaultGroups && !$customGroupsHasBeenDefined) {
$groups = array_merge($groups, $defaultGroups);
}

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

// If you update this check, update accordingly the one in Symfony\Component\PropertyInfo\Extractor\SerializerExtractor::getProperties()
if (
!$ignore
&& (!$groupsHasBeenDefined || array_intersect(array_merge($attributeMetadata->getGroups(), ['*']), $groups))
&& $this->isAllowedAttribute($classOrObject, $name = $attributeMetadata->getName(), null, $context)
) {
// If you update these checks, update accordingly the one in Symfony\Component\PropertyInfo\Extractor\SerializerExtractor::getProperties()
if ($ignore || !$this->isAllowedAttribute($classOrObject, $name = $attributeMetadata->getName(), null, $context)) {
continue;
}

if (!($attributeGroups = $attributeMetadata->getGroups()) && $enableDefaultGroups && !$customGroupsHasBeenDefined) {
$attributeGroups = $defaultGroups;
}

if (!$groupsHasBeenDefined || array_intersect(array_merge($attributeGroups, ['*']), $groups)) {
$allowedAttributes[] = $attributesAsString ? $name : $attributeMetadata;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public function testWithers(array $values)
->withExcludeFromCacheKeys($values[AbstractObjectNormalizer::EXCLUDE_FROM_CACHE_KEY])
->withDeepObjectToPopulate($values[AbstractObjectNormalizer::DEEP_OBJECT_TO_POPULATE])
->withPreserveEmptyObjects($values[AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS])
->withEnableDefaultGroups($values[AbstractObjectNormalizer::ENABLE_DEFAULT_GROUPS])
->toArray();

$this->assertSame($values, $context);
Expand All @@ -65,6 +66,7 @@ public static function withersDataProvider(): iterable
AbstractObjectNormalizer::EXCLUDE_FROM_CACHE_KEY => ['key'],
AbstractObjectNormalizer::DEEP_OBJECT_TO_POPULATE => true,
AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS => false,
AbstractObjectNormalizer::ENABLE_DEFAULT_GROUPS => false,
]];

yield 'With null values' => [[
Expand All @@ -77,6 +79,7 @@ public static function withersDataProvider(): iterable
AbstractObjectNormalizer::EXCLUDE_FROM_CACHE_KEY => null,
AbstractObjectNormalizer::DEEP_OBJECT_TO_POPULATE => null,
AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS => null,
AbstractObjectNormalizer::ENABLE_DEFAULT_GROUPS => null,
]];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@
*/
class OtherSerializedNameDummy
{
#[Groups(['Default']), SerializedName('renamedDefaultGroup')]
private $defaultGroup;

#[Groups(['OtherSerializedNameDummy']), SerializedName('renamedClassGroup')]
public $classGroup;

#[SerializedName('renamedNoGroup')]
public $noGroup;

#[Groups(['custom']), SerializedName('renamedCustomGroup')]
public $customGroup;

#[Groups(['a'])]
private $buz;

Expand Down
Loading
Loading