From c31acc57c56be73637a6f11c689bdf7136f94eda Mon Sep 17 00:00:00 2001 From: Sergey Danilchenko Date: Wed, 11 Sep 2024 16:12:11 +0200 Subject: [PATCH 01/19] [Serializer] Support serialized names and paths configuration per group --- .../Serializer/Annotation/SerializedName.php | 2 +- .../Serializer/Annotation/SerializedPath.php | 2 +- .../Serializer/Attribute/SerializedName.php | 20 ++- .../Serializer/Attribute/SerializedPath.php | 20 ++- src/Symfony/Component/Serializer/CHANGELOG.md | 1 + .../Serializer/Command/DebugCommand.php | 8 +- .../Serializer/Mapping/AttributeMetadata.php | 84 ++++++++++--- .../Mapping/AttributeMetadataInterface.php | 25 +++- .../Factory/ClassMetadataFactoryCompiler.php | 6 +- .../Factory/CompiledClassMetadataFactory.php | 7 +- .../Mapping/Loader/AttributeLoader.php | 18 ++- .../Mapping/Loader/XmlFileLoader.php | 16 +++ .../Mapping/Loader/YamlFileLoader.php | 20 +++ .../serializer-mapping-1.0.xsd | 31 ++--- .../MetadataAwareNameConverter.php | 29 +++-- .../Normalizer/AbstractObjectNormalizer.php | 8 +- .../Tests/Annotation/SerializedNameTest.php | 8 ++ .../Tests/Annotation/SerializedPathTest.php | 8 ++ .../Tests/Command/DebugCommandTest.php | 78 ++++++------ .../Attributes/SerializedNameDummy.php | 3 + .../Attributes/SerializedPathDummy.php | 3 + .../SerializedPathInConstructorDummy.php | 2 + .../Fixtures/OtherSerializedNameDummy.php | 6 + .../Tests/Fixtures/serialization.xml | 17 +++ .../Tests/Fixtures/serialization.yml | 22 +++- .../Fixtures/serializer.class.metadata.php | 8 +- .../ClassMetadataFactoryCompilerTest.php | 31 ++--- .../Mapping/Loader/AttributeLoaderTest.php | 6 + .../Mapping/Loader/XmlFileLoaderTest.php | 6 + .../Mapping/Loader/YamlFileLoaderTest.php | 6 + .../MetadataAwareNameConverterTest.php | 116 +++++++++++++++++- 31 files changed, 494 insertions(+), 123 deletions(-) diff --git a/src/Symfony/Component/Serializer/Annotation/SerializedName.php b/src/Symfony/Component/Serializer/Annotation/SerializedName.php index 167064a0b9d17..899850fb4cf56 100644 --- a/src/Symfony/Component/Serializer/Annotation/SerializedName.php +++ b/src/Symfony/Component/Serializer/Annotation/SerializedName.php @@ -14,7 +14,7 @@ class_exists(\Symfony\Component\Serializer\Attribute\SerializedName::class); if (false) { - #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] + #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)] class SerializedName extends \Symfony\Component\Serializer\Attribute\SerializedName { } diff --git a/src/Symfony/Component/Serializer/Annotation/SerializedPath.php b/src/Symfony/Component/Serializer/Annotation/SerializedPath.php index 76e4c7eff2c5c..919c2da34efdf 100644 --- a/src/Symfony/Component/Serializer/Annotation/SerializedPath.php +++ b/src/Symfony/Component/Serializer/Annotation/SerializedPath.php @@ -14,7 +14,7 @@ class_exists(\Symfony\Component\Serializer\Attribute\SerializedPath::class); if (false) { - #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] + #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)] class SerializedPath extends \Symfony\Component\Serializer\Attribute\SerializedPath { } diff --git a/src/Symfony/Component/Serializer/Attribute/SerializedName.php b/src/Symfony/Component/Serializer/Attribute/SerializedName.php index f1c6cefe2be64..fa40a575a1a10 100644 --- a/src/Symfony/Component/Serializer/Attribute/SerializedName.php +++ b/src/Symfony/Component/Serializer/Attribute/SerializedName.php @@ -16,23 +16,39 @@ /** * @author Fabien Bourigault */ -#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] +#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)] class SerializedName { + private array $groups; + /** * @param string $serializedName The name of the property as it will be serialized + * @param string|string[] $groups The groups to use when serializing or deserializing */ - public function __construct(private readonly string $serializedName) + public function __construct(private readonly string $serializedName, string|array $groups = []) { if ('' === $serializedName) { throw new InvalidArgumentException(\sprintf('Parameter given to "%s" must be a non-empty string.', self::class)); } + + $this->groups = (array) $groups; + + foreach ($this->groups as $group) { + if (!\is_string($group)) { + throw new InvalidArgumentException(\sprintf('Parameter "groups" given to "%s" must be a string or an array of strings, "%s" given.', static::class, get_debug_type($group))); + } + } } public function getSerializedName(): string { return $this->serializedName; } + + public function getGroups(): array + { + return $this->groups; + } } if (!class_exists(\Symfony\Component\Serializer\Annotation\SerializedName::class, false)) { diff --git a/src/Symfony/Component/Serializer/Attribute/SerializedPath.php b/src/Symfony/Component/Serializer/Attribute/SerializedPath.php index 71254bfb96fab..92d36703d3396 100644 --- a/src/Symfony/Component/Serializer/Attribute/SerializedPath.php +++ b/src/Symfony/Component/Serializer/Attribute/SerializedPath.php @@ -18,27 +18,43 @@ /** * @author Tobias Bönner */ -#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] +#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)] class SerializedPath { private PropertyPath $serializedPath; + private array $groups; + /** * @param string $serializedPath A path using a valid PropertyAccess syntax where the value is stored in a normalized representation + * @param string|string[] $groups The groups to use when serializing or deserializing */ - public function __construct(string $serializedPath) + public function __construct(string $serializedPath, string|array $groups = []) { try { $this->serializedPath = new PropertyPath($serializedPath); } catch (InvalidPropertyPathException $pathException) { throw new InvalidArgumentException(\sprintf('Parameter given to "%s" must be a valid property path.', self::class)); } + + $this->groups = (array) $groups; + + foreach ($this->groups as $group) { + if (!\is_string($group)) { + throw new InvalidArgumentException(\sprintf('Parameter "groups" given to "%s" must be a string or an array of strings, "%s" given.', static::class, get_debug_type($group))); + } + } } public function getSerializedPath(): PropertyPath { return $this->serializedPath; } + + public function getGroups(): array + { + return $this->groups; + } } if (!class_exists(\Symfony\Component\Serializer\Annotation\SerializedPath::class, false)) { diff --git a/src/Symfony/Component/Serializer/CHANGELOG.md b/src/Symfony/Component/Serializer/CHANGELOG.md index cb0466ada53c1..dce2c0b0e4e21 100644 --- a/src/Symfony/Component/Serializer/CHANGELOG.md +++ b/src/Symfony/Component/Serializer/CHANGELOG.md @@ -8,6 +8,7 @@ CHANGELOG * Deprecate `CsvEncoderContextBuilder::withEscapeChar()` method * Add `SnakeCaseToCamelCaseNameConverter` * Support subclasses of `\DateTime` and `\DateTimeImmutable` for denormalization + * Support serialized names and paths configuration per group 7.1 --- diff --git a/src/Symfony/Component/Serializer/Command/DebugCommand.php b/src/Symfony/Component/Serializer/Command/DebugCommand.php index 3e70c93a574b7..e29292ccc10b4 100644 --- a/src/Symfony/Component/Serializer/Command/DebugCommand.php +++ b/src/Symfony/Component/Serializer/Command/DebugCommand.php @@ -101,8 +101,12 @@ private function getAttributesData(ClassMetadataInterface $classMetadata): array $data[$attributeMetadata->getName()] = [ 'groups' => $attributeMetadata->getGroups(), 'maxDepth' => $attributeMetadata->getMaxDepth(), - 'serializedName' => $attributeMetadata->getSerializedName(), - 'serializedPath' => $attributeMetadata->getSerializedPath() ? (string) $attributeMetadata->getSerializedPath() : null, + 'serializedNames' => method_exists($attributeMetadata, 'getSerializedNames') + ? $attributeMetadata->getSerializedNames() + : array_filter(['*' => $attributeMetadata->getSerializedName()]), + 'serializedPaths' => method_exists($attributeMetadata, 'getSerializedPaths') + ? array_map('strval', $attributeMetadata->getSerializedPaths()) + : array_filter(['*' => (string) $attributeMetadata->getSerializedPath()]), 'ignore' => $attributeMetadata->isIgnored(), 'normalizationContexts' => $attributeMetadata->getNormalizationContexts(), 'denormalizationContexts' => $attributeMetadata->getDenormalizationContexts(), diff --git a/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php b/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php index 12d1cf6bfd59e..778737471173e 100644 --- a/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php +++ b/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php @@ -40,18 +40,22 @@ class AttributeMetadata implements AttributeMetadataInterface public ?int $maxDepth = null; /** + * @var string[] Serialized names per group name ("*" applies to all groups) + * * @internal This property is public in order to reduce the size of the * class' serialized representation. Do not access it. Use - * {@link getSerializedName()} instead. + * {@link getSerializedNames()} instead. */ - public ?string $serializedName = null; + public array $serializedNames = []; /** + * @var PropertyPath[] Serialized paths per group name ("*" applies to all groups) + * * @internal This property is public in order to reduce the size of the * class' serialized representation. Do not access it. Use - * {@link getSerializedPath()} instead. + * {@link getSerializedPaths()} instead. */ - public ?PropertyPath $serializedPath = null; + public array $serializedPaths = []; /** * @internal This property is public in order to reduce the size of the @@ -110,24 +114,66 @@ public function getMaxDepth(): ?int return $this->maxDepth; } - public function setSerializedName(?string $serializedName): void + public function setSerializedName(?string $serializedName, /* array $groups = [] */): void + { + $groups = 2 <= \func_num_args() ? \func_get_arg(1) : []; + + if (!$groups) { + $this->serializedNames['*'] = $serializedName; + } + + foreach ($groups as $group) { + $this->serializedNames[$group] = $serializedName; + } + } + + public function getSerializedName(/* array $groups = [] */): ?string + { + $groups = 1 <= \func_num_args() ? \func_get_arg(0) : []; + + foreach ($groups as $group) { + if (isset($this->serializedNames[$group])) { + return $this->serializedNames[$group]; + } + } + + return $this->serializedNames['*'] ?? null; + } + + public function getSerializedNames(): array { - $this->serializedName = $serializedName; + return $this->serializedNames; } - public function getSerializedName(): ?string + public function setSerializedPath(?PropertyPath $serializedPath = null, /* array $groups = [] */): void { - return $this->serializedName; + $groups = 2 <= \func_num_args() ? \func_get_arg(1) : []; + + if (!$groups) { + $this->serializedPaths['*'] = $serializedPath; + } + + foreach ($groups as $group) { + $this->serializedPaths[$group] = $serializedPath; + } } - public function setSerializedPath(?PropertyPath $serializedPath = null): void + public function getSerializedPath(/* array $groups = [] */): ?PropertyPath { - $this->serializedPath = $serializedPath; + $groups = 1 <= \func_num_args() ? \func_get_arg(0) : []; + + foreach ($groups as $group) { + if (isset($this->serializedPaths[$group])) { + return $this->serializedPaths[$group]; + } + } + + return $this->serializedPaths['*'] ?? null; } - public function getSerializedPath(): ?PropertyPath + public function getSerializedPaths(): array { - return $this->serializedPath; + return $this->serializedPaths; } public function setIgnore(bool $ignore): void @@ -200,8 +246,16 @@ public function merge(AttributeMetadataInterface $attributeMetadata): void // Overwrite only if not defined $this->maxDepth ??= $attributeMetadata->getMaxDepth(); - $this->serializedName ??= $attributeMetadata->getSerializedName(); - $this->serializedPath ??= $attributeMetadata->getSerializedPath(); + + // Overwrite only if both serialized names and paths are empty + if (!$this->serializedNames && !$this->serializedPaths) { + $this->serializedNames = method_exists($attributeMetadata, 'getSerializedNames') + ? $attributeMetadata->getSerializedNames() + : array_filter(['*' => $attributeMetadata->getSerializedName()]); + $this->serializedPaths = method_exists($attributeMetadata, 'getSerializedPaths') + ? $attributeMetadata->getSerializedPaths() + : array_filter(['*' => $attributeMetadata->getSerializedPaths()]); + } // Overwrite only if both contexts are empty if (!$this->normalizationContexts && !$this->denormalizationContexts) { @@ -221,6 +275,6 @@ public function merge(AttributeMetadataInterface $attributeMetadata): void */ public function __sleep(): array { - return ['name', 'groups', 'maxDepth', 'serializedName', 'serializedPath', 'ignore', 'normalizationContexts', 'denormalizationContexts']; + return ['name', 'groups', 'maxDepth', 'serializedNames', 'serializedPaths', 'ignore', 'normalizationContexts', 'denormalizationContexts']; } } diff --git a/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php b/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php index 9d430602c50a0..1ca5218e43df5 100644 --- a/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php +++ b/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php @@ -21,6 +21,9 @@ * @internal * * @author Kévin Dunglas + * + * @method string[] getSerializedNames() Gets all the serialized names per group ("*" being the base name applied to all groups). + * @method PropertyPath[] getSerializedPaths() Gets all the serialized paths per group ("*" being the base path applied to all groups). */ interface AttributeMetadataInterface { @@ -53,17 +56,31 @@ public function getMaxDepth(): ?int; /** * Sets the serialization name for this attribute. + * + * @param string[] $groups */ - public function setSerializedName(?string $serializedName): void; + public function setSerializedName(?string $serializedName, /* array $groups = [] */): void; /** * Gets the serialization name for this attribute. + * + * @param string[] $groups */ - public function getSerializedName(): ?string; + public function getSerializedName(/* array $groups = [] */): ?string; - public function setSerializedPath(?PropertyPath $serializedPath): void; + /** + * Sets the serialization path for this attribute. + * + * @param string[] $groups + */ + public function setSerializedPath(?PropertyPath $serializedPath, /* array $groups = [] */): void; - public function getSerializedPath(): ?PropertyPath; + /** + * Gets the serialization path for this attribute. + * + * @param string[] $groups + */ + public function getSerializedPath(/* array $groups = [] */): ?PropertyPath; /** * Sets if this attribute must be ignored or not. diff --git a/src/Symfony/Component/Serializer/Mapping/Factory/ClassMetadataFactoryCompiler.php b/src/Symfony/Component/Serializer/Mapping/Factory/ClassMetadataFactoryCompiler.php index 1e9202b7d81fb..0d600f68a07f5 100644 --- a/src/Symfony/Component/Serializer/Mapping/Factory/ClassMetadataFactoryCompiler.php +++ b/src/Symfony/Component/Serializer/Mapping/Factory/ClassMetadataFactoryCompiler.php @@ -47,8 +47,10 @@ private function generateDeclaredClassMetadata(array $classMetadatas): string $attributesMetadata[$attributeMetadata->getName()] = [ $attributeMetadata->getGroups(), $attributeMetadata->getMaxDepth(), - $attributeMetadata->getSerializedName(), - $attributeMetadata->getSerializedPath(), + method_exists($attributeMetadata, 'getSerializedNames') + ? $attributeMetadata->getSerializedNames() : $attributeMetadata->getSerializedName(), + method_exists($attributeMetadata, 'getSerializedPaths') + ? $attributeMetadata->getSerializedPaths() : $attributeMetadata->getSerializedPath(), ]; } diff --git a/src/Symfony/Component/Serializer/Mapping/Factory/CompiledClassMetadataFactory.php b/src/Symfony/Component/Serializer/Mapping/Factory/CompiledClassMetadataFactory.php index ec25d74406d49..1a60ef7d4b475 100644 --- a/src/Symfony/Component/Serializer/Mapping/Factory/CompiledClassMetadataFactory.php +++ b/src/Symfony/Component/Serializer/Mapping/Factory/CompiledClassMetadataFactory.php @@ -53,7 +53,12 @@ public function getMetadataFor(string|object $value): ClassMetadataInterface $classMetadata = new ClassMetadata($className); foreach ($this->compiledClassMetadata[$className][0] as $name => $compiledAttributesMetadata) { $classMetadata->attributesMetadata[$name] = $attributeMetadata = new AttributeMetadata($name); - [$attributeMetadata->groups, $attributeMetadata->maxDepth, $attributeMetadata->serializedName] = $compiledAttributesMetadata; + [$attributeMetadata->groups, $attributeMetadata->maxDepth, $serializedNames] = $compiledAttributesMetadata; + $attributeMetadata->serializedNames = match (true) { + is_array($serializedNames) => $serializedNames, + is_null($serializedNames) => [], + default => ['*' => $serializedNames], + }; } $classMetadata->classDiscriminatorMapping = $this->compiledClassMetadata[$className][1] ? new ClassDiscriminatorMapping(...$this->compiledClassMetadata[$className][1]) diff --git a/src/Symfony/Component/Serializer/Mapping/Loader/AttributeLoader.php b/src/Symfony/Component/Serializer/Mapping/Loader/AttributeLoader.php index 272e236b6226f..24bee62bc8671 100644 --- a/src/Symfony/Component/Serializer/Mapping/Loader/AttributeLoader.php +++ b/src/Symfony/Component/Serializer/Mapping/Loader/AttributeLoader.php @@ -100,9 +100,9 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool } elseif ($annotation instanceof MaxDepth) { $attributesMetadata[$property->name]->setMaxDepth($annotation->getMaxDepth()); } elseif ($annotation instanceof SerializedName) { - $attributesMetadata[$property->name]->setSerializedName($annotation->getSerializedName()); + $this->setAttributeSerializedNameForGroups($annotation, $attributesMetadata[$property->name]); } elseif ($annotation instanceof SerializedPath) { - $attributesMetadata[$property->name]->setSerializedPath($annotation->getSerializedPath()); + $this->setAttributeSerializedPathForGroups($annotation, $attributesMetadata[$property->name]); } elseif ($annotation instanceof Ignore) { $attributesMetadata[$property->name]->setIgnore(true); } elseif ($annotation instanceof Context) { @@ -155,13 +155,13 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool throw new MappingException(\sprintf('SerializedName on "%s::%s()" cannot be added. SerializedName can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); } - $attributeMetadata->setSerializedName($annotation->getSerializedName()); + $this->setAttributeSerializedNameForGroups($annotation, $attributeMetadata); } elseif ($annotation instanceof SerializedPath) { if (!$accessorOrMutator) { throw new MappingException(\sprintf('SerializedPath on "%s::%s()" cannot be added. SerializedPath can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); } - $attributeMetadata->setSerializedPath($annotation->getSerializedPath()); + $this->setAttributeSerializedPathForGroups($annotation, $attributeMetadata); } elseif ($annotation instanceof Ignore) { if ($accessorOrMutator) { $attributeMetadata->setIgnore(true); @@ -204,6 +204,16 @@ private function loadAttributes(\ReflectionMethod|\ReflectionClass|\ReflectionPr } } + private function setAttributeSerializedNameForGroups(SerializedName $annotation, AttributeMetadataInterface $attributeMetadata): void + { + $attributeMetadata->setSerializedName($annotation->getSerializedName(), $annotation->getGroups()); + } + + private function setAttributeSerializedPathForGroups(SerializedPath $annotation, AttributeMetadataInterface $attributeMetadata): void + { + $attributeMetadata->setSerializedPath($annotation->getSerializedPath(), $annotation->getGroups()); + } + private function setAttributeContextsForGroups(Context $annotation, AttributeMetadataInterface $attributeMetadata): void { if ($annotation->getContext()) { diff --git a/src/Symfony/Component/Serializer/Mapping/Loader/XmlFileLoader.php b/src/Symfony/Component/Serializer/Mapping/Loader/XmlFileLoader.php index 44ba89df18072..3ff7be7fe16f6 100644 --- a/src/Symfony/Component/Serializer/Mapping/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/Serializer/Mapping/Loader/XmlFileLoader.php @@ -78,6 +78,22 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool $attributeMetadata->setIgnore(XmlUtils::phpize($attribute['ignore'])); } + foreach ($attribute->serialized as $mapping) { + $groups = (array) $mapping->group; + + if (isset($mapping['name'])) { + $attributeMetadata->setSerializedName((string) $mapping['name'], $groups); + } + + if (isset($mapping['path'])) { + try { + $attributeMetadata->setSerializedPath(new PropertyPath((string) $mapping['path']), $groups); + } catch (InvalidPropertyPathException) { + throw new MappingException(\sprintf('The "path" value must be a valid property path for the attribute "%s" of the class "%s".', $attributeName, $classMetadata->getName())); + } + } + } + foreach ($attribute->context as $node) { $groups = (array) $node->group; $context = $this->parseContext($node->entry); diff --git a/src/Symfony/Component/Serializer/Mapping/Loader/YamlFileLoader.php b/src/Symfony/Component/Serializer/Mapping/Loader/YamlFileLoader.php index ca71cbcbae0b4..5ab0ca3bd0e08 100644 --- a/src/Symfony/Component/Serializer/Mapping/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/Serializer/Mapping/Loader/YamlFileLoader.php @@ -103,6 +103,26 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool $attributeMetadata->setIgnore($data['ignore']); } + foreach ($data['mappings'] ?? [] as $line) { + $groups = $line['groups'] ?? []; + + if ($serializedName = $line['serialized_name'] ?? false) { + if (!\is_string($serializedName) || '' === $serializedName) { + throw new MappingException(\sprintf('The "serialized_name" value must be a non-empty string in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName())); + } + + $attributeMetadata->setSerializedName($serializedName, $groups); + } + + if ($serializedPath = $line['serialized_path'] ?? false) { + try { + $attributeMetadata->setSerializedPath(new PropertyPath((string) $serializedPath), $groups); + } catch (InvalidPropertyPathException) { + throw new MappingException(\sprintf('The "serialized_path" value must be a valid property path in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName())); + } + } + } + foreach ($data['contexts'] ?? [] as $line) { $groups = $line['groups'] ?? []; diff --git a/src/Symfony/Component/Serializer/Mapping/Loader/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd b/src/Symfony/Component/Serializer/Mapping/Loader/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd index f5f6cca9f0f54..e0203fb427990 100644 --- a/src/Symfony/Component/Serializer/Mapping/Loader/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd +++ b/src/Symfony/Component/Serializer/Mapping/Loader/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd @@ -62,6 +62,7 @@ + @@ -74,23 +75,19 @@ - - - - - - - - - - - - - - + + + + + + + + + + @@ -112,4 +109,10 @@ + + + + + + diff --git a/src/Symfony/Component/Serializer/NameConverter/MetadataAwareNameConverter.php b/src/Symfony/Component/Serializer/NameConverter/MetadataAwareNameConverter.php index 2cf1cc9676a38..aaddc9b2d2552 100644 --- a/src/Symfony/Component/Serializer/NameConverter/MetadataAwareNameConverter.php +++ b/src/Symfony/Component/Serializer/NameConverter/MetadataAwareNameConverter.php @@ -47,11 +47,12 @@ 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 @@ -68,7 +69,7 @@ public function denormalize(string $propertyName, ?string $class = null, ?string 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; @@ -79,11 +80,13 @@ private function getCacheValueForNormalization(string $propertyName, string $cla return null; } - if (null !== $attributesMetadata[$propertyName]->getSerializedName() && null !== $attributesMetadata[$propertyName]->getSerializedPath()) { + $contextGroups = (array) ($context[AbstractNormalizer::GROUPS] ?? []); + + if (null !== $attributesMetadata[$propertyName]->getSerializedName($contextGroups) && null !== $attributesMetadata[$propertyName]->getSerializedPath($contextGroups)) { throw new LogicException(\sprintf('Found SerializedName and SerializedPath attributes on property "%s" of class "%s".', $propertyName, $class)); } - return $attributesMetadata[$propertyName]->getSerializedName() ?? null; + return $attributesMetadata[$propertyName]->getSerializedName($contextGroups) ?? null; } private function normalizeFallback(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string @@ -117,22 +120,22 @@ private function getCacheValueForAttributesMetadata(string $class, array $contex $classMetadata = $this->metadataFactory->getMetadataFor($class); + $contextGroups = (array) ($context[AbstractNormalizer::GROUPS] ?? []); + $contextGroupsHasBeenDefined = [] !== $contextGroups; + $contextGroups = array_merge($contextGroups, ['Default', (false !== $nsSep = strrpos($class, '\\')) ? substr($class, $nsSep + 1) : $class]); + $cache = []; foreach ($classMetadata->getAttributesMetadata() as $name => $metadata) { - if (null === $metadata->getSerializedName()) { + if (null === $serializedName = $metadata->getSerializedName($contextGroups)) { continue; } - if (null !== $metadata->getSerializedName() && null !== $metadata->getSerializedPath()) { + if (null !== $metadata->getSerializedPath($contextGroups)) { throw new LogicException(\sprintf('Found SerializedName and SerializedPath attributes on property "%s" of class "%s".', $name, $class)); } $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) { continue; } @@ -141,7 +144,7 @@ private function getCacheValueForAttributesMetadata(string $class, array $contex continue; } - $cache[$metadata->getSerializedName()] = $name; + $cache[$serializedName] = $name; } return $cache; diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index 9db2412980930..e6af3927971ca 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -318,7 +318,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a $mappedClass = $this->getMappedClass($normalizedData, $type, $context); - $nestedAttributes = $this->getNestedAttributes($mappedClass); + $nestedAttributes = $this->getNestedAttributes($mappedClass, $context); $nestedData = $originalNestedData = []; $propertyAccessor = PropertyAccess::createPropertyAccessor(); foreach ($nestedAttributes as $property => $serializedPath) { @@ -954,7 +954,7 @@ private function updateData(array $data, string $attribute, mixed $attributeValu return $data; } - if (null !== $classMetadata && null !== $serializedPath = ($attributesMetadata[$attribute] ?? null)?->getSerializedPath()) { + if (null !== $classMetadata && null !== $serializedPath = ($attributesMetadata[$attribute] ?? null)?->getSerializedPath($this->getGroups($context))) { $propertyAccessor = PropertyAccess::createPropertyAccessor(); if ($propertyAccessor->isReadable($data, $serializedPath) && null !== $propertyAccessor->getValue($data, $serializedPath)) { throw new LogicException(\sprintf('The element you are trying to set is already populated: "%s".', (string) $serializedPath)); @@ -1060,7 +1060,7 @@ private function isUninitializedValueError(\Error|UninitializedPropertyException /** * Returns all attributes with a SerializedPath attribute and the respective path. */ - private function getNestedAttributes(string $class): array + private function getNestedAttributes(string $class, array $context): array { if (!$this->classMetadataFactory?->hasMetadataFor($class)) { return []; @@ -1070,7 +1070,7 @@ private function getNestedAttributes(string $class): array $serializedPaths = []; $classMetadata = $this->classMetadataFactory->getMetadataFor($class); foreach ($classMetadata->getAttributesMetadata() as $name => $metadata) { - if (!$serializedPath = $metadata->getSerializedPath()) { + if (!$serializedPath = $metadata->getSerializedPath($this->getGroups($context))) { continue; } $pathIdentifier = implode(',', $serializedPath->getElements()); diff --git a/src/Symfony/Component/Serializer/Tests/Annotation/SerializedNameTest.php b/src/Symfony/Component/Serializer/Tests/Annotation/SerializedNameTest.php index 3a829aecf4f84..20964411805cd 100644 --- a/src/Symfony/Component/Serializer/Tests/Annotation/SerializedNameTest.php +++ b/src/Symfony/Component/Serializer/Tests/Annotation/SerializedNameTest.php @@ -28,6 +28,14 @@ public function testNotAStringSerializedNameParameter() new SerializedName(''); } + public function testInvalidGroupOption() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage(\sprintf('Parameter "groups" given to "%s" must be a string or an array of strings, "stdClass" given', SerializedName::class)); + + new SerializedName('foo', ['fine', new \stdClass()]); + } + public function testSerializedNameParameters() { $foo = new SerializedName('foo'); diff --git a/src/Symfony/Component/Serializer/Tests/Annotation/SerializedPathTest.php b/src/Symfony/Component/Serializer/Tests/Annotation/SerializedPathTest.php index f5bbfa62b600e..f0b3dd1ba33bf 100644 --- a/src/Symfony/Component/Serializer/Tests/Annotation/SerializedPathTest.php +++ b/src/Symfony/Component/Serializer/Tests/Annotation/SerializedPathTest.php @@ -29,6 +29,14 @@ public function testEmptyStringSerializedPathParameter() new SerializedPath(''); } + public function testInvalidGroupOption() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage(\sprintf('Parameter "groups" given to "%s" must be a string or an array of strings, "stdClass" given', SerializedPath::class)); + + new SerializedPath('foo', ['fine', new \stdClass()]); + } + public function testSerializedPath() { $path = '[one][two]'; diff --git a/src/Symfony/Component/Serializer/Tests/Command/DebugCommandTest.php b/src/Symfony/Component/Serializer/Tests/Command/DebugCommandTest.php index 7bfdf93ddd55c..432c98081c225 100644 --- a/src/Symfony/Component/Serializer/Tests/Command/DebugCommandTest.php +++ b/src/Symfony/Component/Serializer/Tests/Command/DebugCommandTest.php @@ -36,43 +36,47 @@ public function testOutputWithClassArgument() Symfony\Component\Serializer\Tests\Dummy\DummyClassOne ------------------------------------------------------ - +----------+---------------------------------------+ - | Property | Options | - +----------+---------------------------------------+ - | code | [ | - | | "groups" => [ | - | | "book:read", | - | | "book:write" | - | | ], | - | | "maxDepth" => 1, | - | | "serializedName" => "identifier", | - | | "serializedPath" => null, | - | | "ignore" => true, | - | | "normalizationContexts" => [ | - | | "*" => [ | - | | "groups" => [ | - | | "book:read" | - | | ] | - | | ] | - | | ], | - | | "denormalizationContexts" => [ | - | | "*" => [ | - | | "groups" => [ | - | | "book:write" | - | | ] | - | | ] | - | | ] | - | | ] | - | name | [ | - | | "groups" => [], | - | | "maxDepth" => null, | - | | "serializedName" => null, | - | | "serializedPath" => "[data][name]", | - | | "ignore" => false, | - | | "normalizationContexts" => [], | - | | "denormalizationContexts" => [] | - | | ] | - +----------+---------------------------------------+ + +----------+-----------------------------------+ + | Property | Options | + +----------+-----------------------------------+ + | code | [ | + | | "groups" => [ | + | | "book:read", | + | | "book:write" | + | | ], | + | | "maxDepth" => 1, | + | | "serializedNames" => [ | + | | "*" => "identifier" | + | | ], | + | | "serializedPaths" => [], | + | | "ignore" => true, | + | | "normalizationContexts" => [ | + | | "*" => [ | + | | "groups" => [ | + | | "book:read" | + | | ] | + | | ] | + | | ], | + | | "denormalizationContexts" => [ | + | | "*" => [ | + | | "groups" => [ | + | | "book:write" | + | | ] | + | | ] | + | | ] | + | | ] | + | name | [ | + | | "groups" => [], | + | | "maxDepth" => null, | + | | "serializedNames" => [], | + | | "serializedPaths" => [ | + | | "*" => "[data][name]" | + | | ], | + | | "ignore" => false, | + | | "normalizationContexts" => [], | + | | "denormalizationContexts" => [] | + | | ] | + +----------+-----------------------------------+ TXT, $tester->getDisplay(true), diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/SerializedNameDummy.php b/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/SerializedNameDummy.php index 27679b00922cf..a9041df379ba2 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/SerializedNameDummy.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/SerializedNameDummy.php @@ -25,6 +25,9 @@ class SerializedNameDummy public $quux; + #[SerializedName('duxi'), SerializedName('duxa', 'a')] + public $duux; + /** * @var self */ diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/SerializedPathDummy.php b/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/SerializedPathDummy.php index 8b627b7926faa..9d2b997562b54 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/SerializedPathDummy.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/SerializedPathDummy.php @@ -23,6 +23,9 @@ class SerializedPathDummy public $seven; + #[SerializedPath('[five][six]'), SerializedPath('[six][five]', 'a')] + public $eleven; + #[SerializedPath('[three][four]')] public function getSeven() { diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/SerializedPathInConstructorDummy.php b/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/SerializedPathInConstructorDummy.php index e463429c0759a..757533f05fee0 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/SerializedPathInConstructorDummy.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/SerializedPathInConstructorDummy.php @@ -17,6 +17,8 @@ class SerializedPathInConstructorDummy { public function __construct( #[SerializedPath('[one][two]')] public $three, + #[SerializedPath('[five][six]'), SerializedPath('[six][five]', 'a')] + public $eleven, ) { } } diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/OtherSerializedNameDummy.php b/src/Symfony/Component/Serializer/Tests/Fixtures/OtherSerializedNameDummy.php index 86fc6ead1fdb1..c72d3adcf4d01 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/OtherSerializedNameDummy.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/OtherSerializedNameDummy.php @@ -22,6 +22,12 @@ class OtherSerializedNameDummy #[Groups(['a'])] private $buz; + #[Groups(['a']), SerializedName('duxi'), SerializedName('duxa', 'a')] + public $duux; + + #[Groups(['i', 'a']), SerializedName('puxi', 'i'), SerializedName('puxa', 'a')] + public $puux; + public function setBuz($buz) { $this->buz = $buz; diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.xml b/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.xml index 512736db4bbe4..6bebc14704a92 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.xml +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.xml @@ -23,15 +23,32 @@ + + + + a + + + + + + a + + + + + a + + diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.yml b/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.yml index 4371016e34be3..b50eb0be49a7b 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.yml +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.yml @@ -15,17 +15,35 @@ foo: serialized_name: 'baz' bar: - serialized_name: 'qux' + mappings: + - serialized_name: 'qux' + duux: + mappings: + - serialized_name: 'duxi' + - serialized_name: 'duxa' + groups: [a] + 'Symfony\Component\Serializer\Tests\Fixtures\Attributes\SerializedPathDummy': attributes: three: serialized_path: '[one][two]' seven: - serialized_path: '[three][four]' + mappings: + - serialized_path: '[three][four]' + eleven: + mappings: + - serialized_path: '[five][six]' + - serialized_path: '[six][five]' + groups: [a] 'Symfony\Component\Serializer\Tests\Fixtures\Attributes\SerializedPathInConstructorDummy': attributes: three: serialized_path: '[one][two]' + eleven: + serialized_path: '[five][six]' + mappings: + - serialized_path: '[six][five]' + groups: [a] 'Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummy': discriminator_map: type_property: type diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/serializer.class.metadata.php b/src/Symfony/Component/Serializer/Tests/Fixtures/serializer.class.metadata.php index 4773d22675091..d05977f69c47c 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/serializer.class.metadata.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/serializer.class.metadata.php @@ -5,10 +5,10 @@ return [ 'Symfony\Component\Serializer\Tests\Fixtures\Dummy' => [ [ - 'foo' => [[], null, null], - 'bar' => [[], null, null], - 'baz' => [[], null, null], - 'qux' => [[], null, null], + 'foo' => [[], null, []], + 'bar' => [[], null, []], + 'baz' => [[], null, []], + 'qux' => [[], null, []], ], null, ], diff --git a/src/Symfony/Component/Serializer/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php b/src/Symfony/Component/Serializer/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php index 40dcb50152c66..8ed2662ffebd1 100644 --- a/src/Symfony/Component/Serializer/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php @@ -61,10 +61,10 @@ public function testItDumpMetadata() $this->assertArrayHasKey(Dummy::class, $compiledMetadata); $this->assertEquals([ [ - 'foo' => [[], null, null, null], - 'bar' => [[], null, null, null], - 'baz' => [[], null, null, null], - 'qux' => [[], null, null, null], + 'foo' => [[], null, [], []], + 'bar' => [[], null, [], []], + 'baz' => [[], null, [], []], + 'qux' => [[], null, [], []], ], null, ], $compiledMetadata[Dummy::class]); @@ -72,9 +72,9 @@ public function testItDumpMetadata() $this->assertArrayHasKey(MaxDepthDummy::class, $compiledMetadata); $this->assertEquals([ [ - 'foo' => [[], 2, null, null], - 'bar' => [[], 3, null, null], - 'child' => [[], null, null, null], + 'foo' => [[], 2, [], []], + 'bar' => [[], 3, [], []], + 'child' => [[], null, [], []], ], null, ], $compiledMetadata[MaxDepthDummy::class]); @@ -82,10 +82,11 @@ public function testItDumpMetadata() $this->assertArrayHasKey(SerializedNameDummy::class, $compiledMetadata); $this->assertEquals([ [ - 'foo' => [[], null, 'baz', null], - 'bar' => [[], null, 'qux', null], - 'quux' => [[], null, null, null], - 'child' => [[], null, null, null], + 'foo' => [[], null, ['*' => 'baz'], []], + 'bar' => [[], null, ['*' => 'qux'], []], + 'quux' => [[], null, [], []], + 'duux' => [[], null, ['*' => 'duxi', 'a' => 'duxa'], []], + 'child' => [[], null, [], []], ], null, ], $compiledMetadata[SerializedNameDummy::class]); @@ -93,8 +94,9 @@ public function testItDumpMetadata() $this->assertArrayHasKey(SerializedPathDummy::class, $compiledMetadata); $this->assertEquals([ [ - 'three' => [[], null, null, '[one][two]'], - 'seven' => [[], null, null, '[three][four]'], + 'three' => [[], null, [], ['*' => '[one][two]']], + 'seven' => [[], null, [], ['*' => '[three][four]']], + 'eleven' => [[], null, [], ['*' => '[five][six]', 'a' => '[six][five]']], ], null, ], $compiledMetadata[SerializedPathDummy::class]); @@ -102,7 +104,8 @@ public function testItDumpMetadata() $this->assertArrayHasKey(SerializedPathInConstructorDummy::class, $compiledMetadata); $this->assertEquals([ [ - 'three' => [[], null, null, '[one][two]'], + 'three' => [[], null, [], ['*' => '[one][two]']], + 'eleven' => [[], null, [], ['*' => '[five][six]', 'a' => '[six][five]']], ], null, ], $compiledMetadata[SerializedPathInConstructorDummy::class]); diff --git a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/AttributeLoaderTest.php b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/AttributeLoaderTest.php index 5b6ef3de7f8f3..5247b21903afc 100644 --- a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/AttributeLoaderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/AttributeLoaderTest.php @@ -110,6 +110,8 @@ public function testLoadSerializedName() $attributesMetadata = $classMetadata->getAttributesMetadata(); $this->assertEquals('baz', $attributesMetadata['foo']->getSerializedName()); $this->assertEquals('qux', $attributesMetadata['bar']->getSerializedName()); + $this->assertEquals('duxi', $attributesMetadata['duux']->getSerializedName()); + $this->assertEquals('duxa', $attributesMetadata['duux']->getSerializedName(['a'])); } public function testLoadSerializedPath() @@ -120,6 +122,8 @@ public function testLoadSerializedPath() $attributesMetadata = $classMetadata->getAttributesMetadata(); $this->assertEquals(new PropertyPath('[one][two]'), $attributesMetadata['three']->getSerializedPath()); $this->assertEquals(new PropertyPath('[three][four]'), $attributesMetadata['seven']->getSerializedPath()); + $this->assertEquals(new PropertyPath('[five][six]'), $attributesMetadata['eleven']->getSerializedPath()); + $this->assertEquals(new PropertyPath('[six][five]'), $attributesMetadata['eleven']->getSerializedPath(['a'])); } public function testLoadSerializedPathInConstructor() @@ -129,6 +133,8 @@ public function testLoadSerializedPathInConstructor() $attributesMetadata = $classMetadata->getAttributesMetadata(); $this->assertEquals(new PropertyPath('[one][two]'), $attributesMetadata['three']->getSerializedPath()); + $this->assertEquals(new PropertyPath('[five][six]'), $attributesMetadata['eleven']->getSerializedPath()); + $this->assertEquals(new PropertyPath('[six][five]'), $attributesMetadata['eleven']->getSerializedPath(['a'])); } public function testLoadClassMetadataAndMerge() diff --git a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/XmlFileLoaderTest.php index c0298129efa59..9f3e2ca0beef1 100644 --- a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/XmlFileLoaderTest.php @@ -80,6 +80,8 @@ public function testSerializedName() $attributesMetadata = $classMetadata->getAttributesMetadata(); $this->assertEquals('baz', $attributesMetadata['foo']->getSerializedName()); $this->assertEquals('qux', $attributesMetadata['bar']->getSerializedName()); + $this->assertEquals('duxi', $attributesMetadata['duux']->getSerializedName()); + $this->assertEquals('duxa', $attributesMetadata['duux']->getSerializedName(['a'])); } public function testSerializedPath() @@ -90,6 +92,8 @@ public function testSerializedPath() $attributesMetadata = $classMetadata->getAttributesMetadata(); $this->assertEquals('[one][two]', $attributesMetadata['three']->getSerializedPath()); $this->assertEquals('[three][four]', $attributesMetadata['seven']->getSerializedPath()); + $this->assertEquals('[five][six]', $attributesMetadata['eleven']->getSerializedPath()); + $this->assertEquals('[six][five]', $attributesMetadata['eleven']->getSerializedPath(['a'])); } public function testSerializedPathInConstructor() @@ -99,6 +103,8 @@ public function testSerializedPathInConstructor() $attributesMetadata = $classMetadata->getAttributesMetadata(); $this->assertEquals('[one][two]', $attributesMetadata['three']->getSerializedPath()); + $this->assertEquals('[five][six]', $attributesMetadata['eleven']->getSerializedPath()); + $this->assertEquals('[six][five]', $attributesMetadata['eleven']->getSerializedPath(['a'])); } public function testLoadDiscriminatorMap() diff --git a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/YamlFileLoaderTest.php index 48e95aecd9245..7bb18a10d4391 100644 --- a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/YamlFileLoaderTest.php @@ -97,6 +97,8 @@ public function testSerializedName() $attributesMetadata = $classMetadata->getAttributesMetadata(); $this->assertEquals('baz', $attributesMetadata['foo']->getSerializedName()); $this->assertEquals('qux', $attributesMetadata['bar']->getSerializedName()); + $this->assertEquals('duxi', $attributesMetadata['duux']->getSerializedName()); + $this->assertEquals('duxa', $attributesMetadata['duux']->getSerializedName(['a'])); } public function testSerializedPath() @@ -107,6 +109,8 @@ public function testSerializedPath() $attributesMetadata = $classMetadata->getAttributesMetadata(); $this->assertEquals(new PropertyPath('[one][two]'), $attributesMetadata['three']->getSerializedPath()); $this->assertEquals(new PropertyPath('[three][four]'), $attributesMetadata['seven']->getSerializedPath()); + $this->assertEquals(new PropertyPath('[five][six]'), $attributesMetadata['eleven']->getSerializedPath()); + $this->assertEquals(new PropertyPath('[six][five]'), $attributesMetadata['eleven']->getSerializedPath(['a'])); } public function testSerializedPathInConstructor() @@ -116,6 +120,8 @@ public function testSerializedPathInConstructor() $attributesMetadata = $classMetadata->getAttributesMetadata(); $this->assertEquals(new PropertyPath('[one][two]'), $attributesMetadata['three']->getSerializedPath()); + $this->assertEquals(new PropertyPath('[five][six]'), $attributesMetadata['eleven']->getSerializedPath()); + $this->assertEquals(new PropertyPath('[six][five]'), $attributesMetadata['eleven']->getSerializedPath(['a'])); } public function testLoadDiscriminatorMap() diff --git a/src/Symfony/Component/Serializer/Tests/NameConverter/MetadataAwareNameConverterTest.php b/src/Symfony/Component/Serializer/Tests/NameConverter/MetadataAwareNameConverterTest.php index c6ccd2601c98e..78fc2ed536e3f 100644 --- a/src/Symfony/Component/Serializer/Tests/NameConverter/MetadataAwareNameConverterTest.php +++ b/src/Symfony/Component/Serializer/Tests/NameConverter/MetadataAwareNameConverterTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Serializer\Tests\NameConverter; use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Attribute\Groups; use Symfony\Component\Serializer\Attribute\SerializedName; use Symfony\Component\Serializer\Attribute\SerializedPath; use Symfony\Component\Serializer\Exception\LogicException; @@ -101,6 +102,7 @@ public static function attributeProvider(): array ['foo', 'baz'], ['bar', 'qux'], ['quux', 'quux'], + ['duux', 'duxi'], [0, 0], ]; } @@ -111,6 +113,7 @@ public static function fallbackAttributeProvider(): array ['foo', 'baz'], ['bar', 'qux'], ['quux', 'QUUX'], + ['duux', 'duxi'], [0, 0], ]; } @@ -127,6 +130,24 @@ public function testNormalizeWithGroups(string $propertyName, string $expected, $this->assertEquals($expected, $nameConverter->normalize($propertyName, OtherSerializedNameDummy::class, null, $context)); } + /** + * @dataProvider fallbackAttributeAndContextProvider + */ + public function testNormalizeWithGroupsAndFallback(string $propertyName, string $expected, array $context = []) + { + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); + + $fallback = $this->createMock(NameConverterInterface::class); + $fallback + ->method('normalize') + ->willReturnCallback(static fn ($propertyName) => strtoupper($propertyName)) + ; + + $nameConverter = new MetadataAwareNameConverter($classMetadataFactory, $fallback); + + $this->assertEquals($expected, $nameConverter->normalize($propertyName, OtherSerializedNameDummy::class, null, $context)); + } + /** * @dataProvider attributeAndContextProvider */ @@ -139,6 +160,24 @@ public function testDenormalizeWithGroups(string $expected, string $propertyName $this->assertEquals($expected, $nameConverter->denormalize($propertyName, OtherSerializedNameDummy::class, null, $context)); } + /** + * @dataProvider fallbackAttributeAndContextProvider + */ + public function testDenormalizeWithGroupsAndFallback(string $expected, string $propertyName, array $context = []) + { + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); + + $fallback = $this->createMock(NameConverterInterface::class); + $fallback + ->method('denormalize') + ->willReturnCallback(static fn ($propertyName) => strtolower($propertyName)) + ; + + $nameConverter = new MetadataAwareNameConverter($classMetadataFactory, $fallback); + + $this->assertEquals($expected, $nameConverter->denormalize($propertyName, OtherSerializedNameDummy::class, null, $context)); + } + public static function attributeAndContextProvider(): array { return [ @@ -149,6 +188,37 @@ public static function attributeAndContextProvider(): array ['buz', 'buz', ['groups' => ['c']]], ['buz', 'buz', []], ['buzForExport', 'buz', ['groups' => ['*']]], + ['duux', 'duxi', []], + ['duux', 'duxi', ['groups' => ['*']]], + ['duux', 'duxa', ['groups' => ['a']]], + ['duux', 'duxi', ['groups' => ['z']]], + ['puux', 'puux', []], + ['puux', 'puux', ['groups' => ['*']]], + ['puux', 'puxi', ['groups' => ['i']]], + ['puux', 'puxa', ['groups' => ['a']]], + ['puux', 'puux', ['groups' => ['z']]], + ]; + } + + public static function fallbackAttributeAndContextProvider(): array + { + return [ + ['buz', 'BUZ', ['groups' => ['a']]], + ['buzForExport', 'buz', ['groups' => ['b']]], + ['buz', 'BUZ', ['groups' => 'a']], + ['buzForExport', 'buz', ['groups' => 'b']], + ['buz', 'BUZ', ['groups' => ['c']]], + ['buz', 'BUZ', []], + ['buzForExport', 'buz', ['groups' => ['*']]], + ['duux', 'duxi', []], + ['duux', 'duxi', ['groups' => ['*']]], + ['duux', 'duxa', ['groups' => ['a']]], + ['duux', 'duxi', ['groups' => ['z']]], + ['puux', 'PUUX', []], + ['puux', 'PUUX', ['groups' => ['*']]], + ['puux', 'puxi', ['groups' => ['i']]], + ['puux', 'puxa', ['groups' => ['a']]], + ['puux', 'PUUX', ['groups' => ['z']]], ]; } @@ -169,7 +239,23 @@ public function testDenormalizeWithNestedPathAndName() $nameConverter = new MetadataAwareNameConverter($classMetadataFactory); $this->expectException(LogicException::class); $this->expectExceptionMessage('Found SerializedName and SerializedPath attributes on property "foo" of class "Symfony\Component\Serializer\Tests\NameConverter\NestedPathAndName".'); - $nameConverter->denormalize('foo', NestedPathAndName::class); + $nameConverter->denormalize('five', NestedPathAndName::class); + } + + public function testDenormalizeWithNestedPathAndNameSameGroup() + { + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); + $nameConverter = new MetadataAwareNameConverter($classMetadataFactory); + $this->expectException(LogicException::class); + $this->expectExceptionMessage('Found SerializedName and SerializedPath attributes on property "bar" of class "Symfony\Component\Serializer\Tests\NameConverter\NestedPathAndNameSameGroup".'); + $nameConverter->denormalize('eight', NestedPathAndNameSameGroup::class, null, ['groups' => 'a']); + } + + public function testDenormalizeWithNestedPathAndNameDifferentGroups() + { + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); + $nameConverter = new MetadataAwareNameConverter($classMetadataFactory); + $this->assertEquals('baz', $nameConverter->denormalize('eleven', NestedPathAndNameDifferentGroups::class, null, ['groups' => 'a'])); } public function testNormalizeWithNestedPathAndName() @@ -180,6 +266,22 @@ public function testNormalizeWithNestedPathAndName() $this->expectExceptionMessage('Found SerializedName and SerializedPath attributes on property "foo" of class "Symfony\Component\Serializer\Tests\NameConverter\NestedPathAndName".'); $nameConverter->normalize('foo', NestedPathAndName::class); } + + public function testNormalizeWithNestedPathAndNameSameGroup() + { + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); + $nameConverter = new MetadataAwareNameConverter($classMetadataFactory); + $this->expectException(LogicException::class); + $this->expectExceptionMessage('Found SerializedName and SerializedPath attributes on property "bar" of class "Symfony\Component\Serializer\Tests\NameConverter\NestedPathAndNameSameGroup".'); + $nameConverter->normalize('bar', NestedPathAndNameSameGroup::class, null, ['groups' => 'a']); + } + + public function testNormalizeWithNestedPathAndNameDifferentGroups() + { + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); + $nameConverter = new MetadataAwareNameConverter($classMetadataFactory); + $this->assertEquals('eleven', $nameConverter->normalize('baz', NestedPathAndNameDifferentGroups::class, null, ['groups' => 'a'])); + } } class NestedPathAndName @@ -187,3 +289,15 @@ class NestedPathAndName #[SerializedName('five'), SerializedPath('[one][two][three]')] public $foo; } + +class NestedPathAndNameSameGroup +{ + #[Groups(['a']), SerializedName('eight', 'a'), SerializedPath('[four][five][six]', 'a')] + public $bar; +} + +class NestedPathAndNameDifferentGroups +{ + #[Groups(['a', 'b']), SerializedName('eleven', 'a'), SerializedPath('[seven][eight][nine]', 'b')] + public $baz; +} From 5f869f9ba0b028cb0bdc0f0b6375d58414bbdf96 Mon Sep 17 00:00:00 2001 From: Sergey Danilchenko Date: Wed, 11 Sep 2024 17:10:06 +0200 Subject: [PATCH 02/19] CS fixes --- .../Serializer/Attribute/SerializedName.php | 4 ++-- .../Serializer/Attribute/SerializedPath.php | 4 ++-- .../Serializer/Mapping/AttributeMetadata.php | 12 ++++++------ .../Mapping/AttributeMetadataInterface.php | 6 +++--- .../Mapping/Factory/CompiledClassMetadataFactory.php | 6 +++--- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Symfony/Component/Serializer/Attribute/SerializedName.php b/src/Symfony/Component/Serializer/Attribute/SerializedName.php index fa40a575a1a10..1ec1143fbbe25 100644 --- a/src/Symfony/Component/Serializer/Attribute/SerializedName.php +++ b/src/Symfony/Component/Serializer/Attribute/SerializedName.php @@ -22,8 +22,8 @@ class SerializedName private array $groups; /** - * @param string $serializedName The name of the property as it will be serialized - * @param string|string[] $groups The groups to use when serializing or deserializing + * @param string $serializedName The name of the property as it will be serialized + * @param string|string[] $groups The groups to use when serializing or deserializing */ public function __construct(private readonly string $serializedName, string|array $groups = []) { diff --git a/src/Symfony/Component/Serializer/Attribute/SerializedPath.php b/src/Symfony/Component/Serializer/Attribute/SerializedPath.php index 92d36703d3396..29ff3e7d43ce4 100644 --- a/src/Symfony/Component/Serializer/Attribute/SerializedPath.php +++ b/src/Symfony/Component/Serializer/Attribute/SerializedPath.php @@ -26,8 +26,8 @@ class SerializedPath private array $groups; /** - * @param string $serializedPath A path using a valid PropertyAccess syntax where the value is stored in a normalized representation - * @param string|string[] $groups The groups to use when serializing or deserializing + * @param string $serializedPath A path using a valid PropertyAccess syntax where the value is stored in a normalized representation + * @param string|string[] $groups The groups to use when serializing or deserializing */ public function __construct(string $serializedPath, string|array $groups = []) { diff --git a/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php b/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php index 778737471173e..8400c4200d2cb 100644 --- a/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php +++ b/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php @@ -114,9 +114,9 @@ public function getMaxDepth(): ?int return $this->maxDepth; } - public function setSerializedName(?string $serializedName, /* array $groups = [] */): void + public function setSerializedName(?string $serializedName /* , array $groups = [] */): void { - $groups = 2 <= \func_num_args() ? \func_get_arg(1) : []; + $groups = 2 <= \func_num_args() ? func_get_arg(1) : []; if (!$groups) { $this->serializedNames['*'] = $serializedName; @@ -129,7 +129,7 @@ public function setSerializedName(?string $serializedName, /* array $groups = [] public function getSerializedName(/* array $groups = [] */): ?string { - $groups = 1 <= \func_num_args() ? \func_get_arg(0) : []; + $groups = 1 <= \func_num_args() ? func_get_arg(0) : []; foreach ($groups as $group) { if (isset($this->serializedNames[$group])) { @@ -145,9 +145,9 @@ public function getSerializedNames(): array return $this->serializedNames; } - public function setSerializedPath(?PropertyPath $serializedPath = null, /* array $groups = [] */): void + public function setSerializedPath(?PropertyPath $serializedPath = null /* , array $groups = [] */): void { - $groups = 2 <= \func_num_args() ? \func_get_arg(1) : []; + $groups = 2 <= \func_num_args() ? func_get_arg(1) : []; if (!$groups) { $this->serializedPaths['*'] = $serializedPath; @@ -160,7 +160,7 @@ public function setSerializedPath(?PropertyPath $serializedPath = null, /* array public function getSerializedPath(/* array $groups = [] */): ?PropertyPath { - $groups = 1 <= \func_num_args() ? \func_get_arg(0) : []; + $groups = 1 <= \func_num_args() ? func_get_arg(0) : []; foreach ($groups as $group) { if (isset($this->serializedPaths[$group])) { diff --git a/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php b/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php index 1ca5218e43df5..cbbc558ab476c 100644 --- a/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php +++ b/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php @@ -22,7 +22,7 @@ * * @author Kévin Dunglas * - * @method string[] getSerializedNames() Gets all the serialized names per group ("*" being the base name applied to all groups). + * @method string[] getSerializedNames() Gets all the serialized names per group ("*" being the base name applied to all groups). * @method PropertyPath[] getSerializedPaths() Gets all the serialized paths per group ("*" being the base path applied to all groups). */ interface AttributeMetadataInterface @@ -59,7 +59,7 @@ public function getMaxDepth(): ?int; * * @param string[] $groups */ - public function setSerializedName(?string $serializedName, /* array $groups = [] */): void; + public function setSerializedName(?string $serializedName /* , array $groups = [] */): void; /** * Gets the serialization name for this attribute. @@ -73,7 +73,7 @@ public function getSerializedName(/* array $groups = [] */): ?string; * * @param string[] $groups */ - public function setSerializedPath(?PropertyPath $serializedPath, /* array $groups = [] */): void; + public function setSerializedPath(?PropertyPath $serializedPath /* , array $groups = [] */): void; /** * Gets the serialization path for this attribute. diff --git a/src/Symfony/Component/Serializer/Mapping/Factory/CompiledClassMetadataFactory.php b/src/Symfony/Component/Serializer/Mapping/Factory/CompiledClassMetadataFactory.php index 1a60ef7d4b475..8f240ffce1d39 100644 --- a/src/Symfony/Component/Serializer/Mapping/Factory/CompiledClassMetadataFactory.php +++ b/src/Symfony/Component/Serializer/Mapping/Factory/CompiledClassMetadataFactory.php @@ -35,7 +35,7 @@ public function __construct( $compiledClassMetadata = require $compiledClassMetadataFile; if (!\is_array($compiledClassMetadata)) { - throw new \RuntimeException(\sprintf('Compiled metadata must be of the type array, %s given.', \gettype($compiledClassMetadata))); + throw new \RuntimeException(\sprintf('Compiled metadata must be of the type array, "%s" given.', \gettype($compiledClassMetadata))); } $this->compiledClassMetadata = $compiledClassMetadata; @@ -55,8 +55,8 @@ public function getMetadataFor(string|object $value): ClassMetadataInterface $classMetadata->attributesMetadata[$name] = $attributeMetadata = new AttributeMetadata($name); [$attributeMetadata->groups, $attributeMetadata->maxDepth, $serializedNames] = $compiledAttributesMetadata; $attributeMetadata->serializedNames = match (true) { - is_array($serializedNames) => $serializedNames, - is_null($serializedNames) => [], + \is_array($serializedNames) => $serializedNames, + null === $serializedNames => [], default => ['*' => $serializedNames], }; } From c7d2c4de0735da467b8c41628816b0cae3d05646 Mon Sep 17 00:00:00 2001 From: Sergey Danilchenko Date: Wed, 11 Sep 2024 17:31:51 +0200 Subject: [PATCH 03/19] Fix static analysis --- .../Component/Serializer/Mapping/AttributeMetadata.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php b/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php index 8400c4200d2cb..f75444e4e536a 100644 --- a/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php +++ b/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php @@ -40,7 +40,7 @@ class AttributeMetadata implements AttributeMetadataInterface public ?int $maxDepth = null; /** - * @var string[] Serialized names per group name ("*" applies to all groups) + * @var array Serialized names per group name ("*" applies to all groups) * * @internal This property is public in order to reduce the size of the * class' serialized representation. Do not access it. Use @@ -49,7 +49,7 @@ class AttributeMetadata implements AttributeMetadataInterface public array $serializedNames = []; /** - * @var PropertyPath[] Serialized paths per group name ("*" applies to all groups) + * @var array Serialized paths per group name ("*" applies to all groups) * * @internal This property is public in order to reduce the size of the * class' serialized representation. Do not access it. Use @@ -254,7 +254,7 @@ public function merge(AttributeMetadataInterface $attributeMetadata): void : array_filter(['*' => $attributeMetadata->getSerializedName()]); $this->serializedPaths = method_exists($attributeMetadata, 'getSerializedPaths') ? $attributeMetadata->getSerializedPaths() - : array_filter(['*' => $attributeMetadata->getSerializedPaths()]); + : array_filter(['*' => $attributeMetadata->getSerializedPath()]); } // Overwrite only if both contexts are empty From bb65ab8df3654feeb6da59fa9a6761243a17191f Mon Sep 17 00:00:00 2001 From: Sergey Danilchenko Date: Fri, 13 Sep 2024 11:15:08 +0200 Subject: [PATCH 04/19] Revert changes to exception message --- .../Serializer/Mapping/Factory/CompiledClassMetadataFactory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Serializer/Mapping/Factory/CompiledClassMetadataFactory.php b/src/Symfony/Component/Serializer/Mapping/Factory/CompiledClassMetadataFactory.php index 8f240ffce1d39..b5f8e80d6bbe2 100644 --- a/src/Symfony/Component/Serializer/Mapping/Factory/CompiledClassMetadataFactory.php +++ b/src/Symfony/Component/Serializer/Mapping/Factory/CompiledClassMetadataFactory.php @@ -35,7 +35,7 @@ public function __construct( $compiledClassMetadata = require $compiledClassMetadataFile; if (!\is_array($compiledClassMetadata)) { - throw new \RuntimeException(\sprintf('Compiled metadata must be of the type array, "%s" given.', \gettype($compiledClassMetadata))); + throw new \RuntimeException(\sprintf('Compiled metadata must be of the type array, %s given.', \gettype($compiledClassMetadata))); } $this->compiledClassMetadata = $compiledClassMetadata; From 223ad893cf076b7e3da4b65e0473c0f46d8bb6a9 Mon Sep 17 00:00:00 2001 From: Sergey Danilchenko Date: Thu, 19 Sep 2024 11:48:45 +0200 Subject: [PATCH 05/19] Attribute metadata: default asterisk group in serialized name/path getters and setters --- .../Serializer/Mapping/AttributeMetadata.php | 16 ++++++++-------- .../Mapping/AttributeMetadataInterface.php | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php b/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php index f75444e4e536a..2442b0ede1c4f 100644 --- a/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php +++ b/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php @@ -114,9 +114,9 @@ public function getMaxDepth(): ?int return $this->maxDepth; } - public function setSerializedName(?string $serializedName /* , array $groups = [] */): void + public function setSerializedName(?string $serializedName /* , array $groups = ['*'] */): void { - $groups = 2 <= \func_num_args() ? func_get_arg(1) : []; + $groups = 2 <= \func_num_args() ? func_get_arg(1) : ['*']; if (!$groups) { $this->serializedNames['*'] = $serializedName; @@ -127,9 +127,9 @@ public function setSerializedName(?string $serializedName /* , array $groups = [ } } - public function getSerializedName(/* array $groups = [] */): ?string + public function getSerializedName(/* array $groups = ['*'] */): ?string { - $groups = 1 <= \func_num_args() ? func_get_arg(0) : []; + $groups = 1 <= \func_num_args() ? func_get_arg(0) : ['*']; foreach ($groups as $group) { if (isset($this->serializedNames[$group])) { @@ -145,9 +145,9 @@ public function getSerializedNames(): array return $this->serializedNames; } - public function setSerializedPath(?PropertyPath $serializedPath = null /* , array $groups = [] */): void + public function setSerializedPath(?PropertyPath $serializedPath = null /* , array $groups = ['*'] */): void { - $groups = 2 <= \func_num_args() ? func_get_arg(1) : []; + $groups = 2 <= \func_num_args() ? func_get_arg(1) : ['*']; if (!$groups) { $this->serializedPaths['*'] = $serializedPath; @@ -158,9 +158,9 @@ public function setSerializedPath(?PropertyPath $serializedPath = null /* , arra } } - public function getSerializedPath(/* array $groups = [] */): ?PropertyPath + public function getSerializedPath(/* array $groups = ['*'] */): ?PropertyPath { - $groups = 1 <= \func_num_args() ? func_get_arg(0) : []; + $groups = 1 <= \func_num_args() ? func_get_arg(0) : ['*']; foreach ($groups as $group) { if (isset($this->serializedPaths[$group])) { diff --git a/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php b/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php index cbbc558ab476c..aa5e7d55afab3 100644 --- a/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php +++ b/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php @@ -59,28 +59,28 @@ public function getMaxDepth(): ?int; * * @param string[] $groups */ - public function setSerializedName(?string $serializedName /* , array $groups = [] */): void; + public function setSerializedName(?string $serializedName /* , array $groups = ['*'] */): void; /** * Gets the serialization name for this attribute. * * @param string[] $groups */ - public function getSerializedName(/* array $groups = [] */): ?string; + public function getSerializedName(/* array $groups = ['*'] */): ?string; /** * Sets the serialization path for this attribute. * * @param string[] $groups */ - public function setSerializedPath(?PropertyPath $serializedPath /* , array $groups = [] */): void; + public function setSerializedPath(?PropertyPath $serializedPath /* , array $groups = ['*'] */): void; /** * Gets the serialization path for this attribute. * * @param string[] $groups */ - public function getSerializedPath(/* array $groups = [] */): ?PropertyPath; + public function getSerializedPath(/* array $groups = ['*'] */): ?PropertyPath; /** * Sets if this attribute must be ignored or not. From 796ed93bfdc0a246901d22f6a6b26e528e9250f8 Mon Sep 17 00:00:00 2001 From: Sergey Danilchenko Date: Thu, 19 Sep 2024 12:12:18 +0200 Subject: [PATCH 06/19] Attribute metadata: properly unset data if given serialized name/path is null --- .../Serializer/Mapping/AttributeMetadata.php | 36 ++++++++++++++----- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php b/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php index 2442b0ede1c4f..0824c78671b50 100644 --- a/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php +++ b/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php @@ -40,7 +40,7 @@ class AttributeMetadata implements AttributeMetadataInterface public ?int $maxDepth = null; /** - * @var array Serialized names per group name ("*" applies to all groups) + * @var array Serialized names per group name ("*" applies to all groups) * * @internal This property is public in order to reduce the size of the * class' serialized representation. Do not access it. Use @@ -49,7 +49,7 @@ class AttributeMetadata implements AttributeMetadataInterface public array $serializedNames = []; /** - * @var array Serialized paths per group name ("*" applies to all groups) + * @var array Serialized paths per group name ("*" applies to all groups) * * @internal This property is public in order to reduce the size of the * class' serialized representation. Do not access it. Use @@ -119,11 +119,21 @@ public function setSerializedName(?string $serializedName /* , array $groups = [ $groups = 2 <= \func_num_args() ? func_get_arg(1) : ['*']; if (!$groups) { - $this->serializedNames['*'] = $serializedName; + if (isset($serializedName)) { + $this->serializedNames['*'] = $serializedName; + } else { + unset($this->serializedNames['*']); + } } - foreach ($groups as $group) { - $this->serializedNames[$group] = $serializedName; + if (isset($serializedName)) { + foreach ($groups as $group) { + $this->serializedNames[$group] = $serializedName; + } + } else { + foreach ($groups as $group) { + unset($this->serializedNames[$group]); + } } } @@ -150,11 +160,21 @@ public function setSerializedPath(?PropertyPath $serializedPath = null /* , arra $groups = 2 <= \func_num_args() ? func_get_arg(1) : ['*']; if (!$groups) { - $this->serializedPaths['*'] = $serializedPath; + if (isset($serializedPath)) { + $this->serializedPaths['*'] = $serializedPath; + } else { + unset($this->serializedPaths['*']); + } } - foreach ($groups as $group) { - $this->serializedPaths[$group] = $serializedPath; + if (isset($serializedPath)) { + foreach ($groups as $group) { + $this->serializedPaths[$group] = $serializedPath; + } + } else { + foreach ($groups as $group) { + unset($this->serializedPaths[$group]); + } } } From 0b12b51d72f9b76be1b73f22c4c914c00ba67aa6 Mon Sep 17 00:00:00 2001 From: Sergey Danilchenko Date: Thu, 19 Sep 2024 12:31:53 +0200 Subject: [PATCH 07/19] Attribute metadata: get rid of unsafe array_filter function usage --- .../Serializer/Command/DebugCommand.php | 9 ++-- .../Serializer/Mapping/AttributeMetadata.php | 44 ++++++++++++++++--- 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/src/Symfony/Component/Serializer/Command/DebugCommand.php b/src/Symfony/Component/Serializer/Command/DebugCommand.php index e29292ccc10b4..e23dd8fd2a7ce 100644 --- a/src/Symfony/Component/Serializer/Command/DebugCommand.php +++ b/src/Symfony/Component/Serializer/Command/DebugCommand.php @@ -19,6 +19,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Serializer\Mapping\AttributeMetadata; use Symfony\Component\Serializer\Mapping\ClassMetadataInterface; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; @@ -101,12 +102,8 @@ private function getAttributesData(ClassMetadataInterface $classMetadata): array $data[$attributeMetadata->getName()] = [ 'groups' => $attributeMetadata->getGroups(), 'maxDepth' => $attributeMetadata->getMaxDepth(), - 'serializedNames' => method_exists($attributeMetadata, 'getSerializedNames') - ? $attributeMetadata->getSerializedNames() - : array_filter(['*' => $attributeMetadata->getSerializedName()]), - 'serializedPaths' => method_exists($attributeMetadata, 'getSerializedPaths') - ? array_map('strval', $attributeMetadata->getSerializedPaths()) - : array_filter(['*' => (string) $attributeMetadata->getSerializedPath()]), + 'serializedNames' => AttributeMetadata::getSerializedNamesFromAttributeMetadata($attributeMetadata), + 'serializedPaths' => array_map('strval', AttributeMetadata::getSerializedPathsFromAttributeMetadata($attributeMetadata)), 'ignore' => $attributeMetadata->isIgnored(), 'normalizationContexts' => $attributeMetadata->getNormalizationContexts(), 'denormalizationContexts' => $attributeMetadata->getDenormalizationContexts(), diff --git a/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php b/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php index 0824c78671b50..a477424e0e942 100644 --- a/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php +++ b/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php @@ -269,12 +269,8 @@ public function merge(AttributeMetadataInterface $attributeMetadata): void // Overwrite only if both serialized names and paths are empty if (!$this->serializedNames && !$this->serializedPaths) { - $this->serializedNames = method_exists($attributeMetadata, 'getSerializedNames') - ? $attributeMetadata->getSerializedNames() - : array_filter(['*' => $attributeMetadata->getSerializedName()]); - $this->serializedPaths = method_exists($attributeMetadata, 'getSerializedPaths') - ? $attributeMetadata->getSerializedPaths() - : array_filter(['*' => $attributeMetadata->getSerializedPath()]); + $this->serializedNames = self::getSerializedNamesFromAttributeMetadata($attributeMetadata); + $this->serializedPaths = self::getSerializedPathsFromAttributeMetadata($attributeMetadata); } // Overwrite only if both contexts are empty @@ -288,6 +284,42 @@ public function merge(AttributeMetadataInterface $attributeMetadata): void } } + /** + * @internal + * + * @return array + */ + public static function getSerializedNamesFromAttributeMetadata(AttributeMetadataInterface $attributeMetadata): array + { + if (method_exists($attributeMetadata, 'getSerializedNames')) { + return $attributeMetadata->getSerializedNames(); + } + + if (null !== $serializedName = $attributeMetadata->getSerializedName()) { + return ['*' => $serializedName]; + } + + return []; + } + + /** + * @internal + * + * @return array + */ + public static function getSerializedPathsFromAttributeMetadata(AttributeMetadataInterface $attributeMetadata): array + { + if (method_exists($attributeMetadata, 'getSerializedPaths')) { + return $attributeMetadata->getSerializedPaths(); + } + + if (null !== $serializedPath = $attributeMetadata->getSerializedPath()) { + return ['*' => $serializedPath]; + } + + return []; + } + /** * Returns the names of the properties that should be serialized. * From 7f6ac693656587a48e23addde42d98780fbd39bc Mon Sep 17 00:00:00 2001 From: Sergey Danilchenko Date: Thu, 19 Sep 2024 12:35:51 +0200 Subject: [PATCH 08/19] Attribute metadata: update merging logic --- .../Component/Serializer/Mapping/AttributeMetadata.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php b/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php index a477424e0e942..c87d7ede5f959 100644 --- a/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php +++ b/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php @@ -267,9 +267,13 @@ public function merge(AttributeMetadataInterface $attributeMetadata): void // Overwrite only if not defined $this->maxDepth ??= $attributeMetadata->getMaxDepth(); - // Overwrite only if both serialized names and paths are empty - if (!$this->serializedNames && !$this->serializedPaths) { + // Overwrite only if serialized names are empty + if (!$this->serializedNames) { $this->serializedNames = self::getSerializedNamesFromAttributeMetadata($attributeMetadata); + } + + // Overwrite only if serialized paths are empty + if (!$this->serializedPaths) { $this->serializedPaths = self::getSerializedPathsFromAttributeMetadata($attributeMetadata); } From a46ed52d057c6bb3ed89ffb1f9e5f37c92905c78 Mon Sep 17 00:00:00 2001 From: Sergey Danilchenko Date: Thu, 19 Sep 2024 13:06:34 +0200 Subject: [PATCH 09/19] Default asterisk group in serialized name/path attributes --- src/Symfony/Component/Serializer/Attribute/SerializedName.php | 4 ++-- src/Symfony/Component/Serializer/Attribute/SerializedPath.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/Serializer/Attribute/SerializedName.php b/src/Symfony/Component/Serializer/Attribute/SerializedName.php index 1ec1143fbbe25..195882024e435 100644 --- a/src/Symfony/Component/Serializer/Attribute/SerializedName.php +++ b/src/Symfony/Component/Serializer/Attribute/SerializedName.php @@ -25,13 +25,13 @@ class SerializedName * @param string $serializedName The name of the property as it will be serialized * @param string|string[] $groups The groups to use when serializing or deserializing */ - public function __construct(private readonly string $serializedName, string|array $groups = []) + public function __construct(private readonly string $serializedName, string|array $groups = ['*']) { if ('' === $serializedName) { throw new InvalidArgumentException(\sprintf('Parameter given to "%s" must be a non-empty string.', self::class)); } - $this->groups = (array) $groups; + $this->groups = ((array) $groups) ?: ['*']; foreach ($this->groups as $group) { if (!\is_string($group)) { diff --git a/src/Symfony/Component/Serializer/Attribute/SerializedPath.php b/src/Symfony/Component/Serializer/Attribute/SerializedPath.php index 29ff3e7d43ce4..9c3910754edbd 100644 --- a/src/Symfony/Component/Serializer/Attribute/SerializedPath.php +++ b/src/Symfony/Component/Serializer/Attribute/SerializedPath.php @@ -29,7 +29,7 @@ class SerializedPath * @param string $serializedPath A path using a valid PropertyAccess syntax where the value is stored in a normalized representation * @param string|string[] $groups The groups to use when serializing or deserializing */ - public function __construct(string $serializedPath, string|array $groups = []) + public function __construct(string $serializedPath, string|array $groups = ['*']) { try { $this->serializedPath = new PropertyPath($serializedPath); @@ -37,7 +37,7 @@ public function __construct(string $serializedPath, string|array $groups = []) throw new InvalidArgumentException(\sprintf('Parameter given to "%s" must be a valid property path.', self::class)); } - $this->groups = (array) $groups; + $this->groups = ((array) $groups) ?: ['*']; foreach ($this->groups as $group) { if (!\is_string($group)) { From dcab6c1321522160d5c658586519f5de555bdfd8 Mon Sep 17 00:00:00 2001 From: Sergey Danilchenko Date: Thu, 19 Sep 2024 13:30:00 +0200 Subject: [PATCH 10/19] Attribute metadata: fall back to asterisk group if group list is empty in serialized name/path setters --- .../Serializer/Mapping/AttributeMetadata.php | 20 ++----------------- 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php b/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php index c87d7ede5f959..56404d3a7b6f2 100644 --- a/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php +++ b/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php @@ -116,15 +116,7 @@ public function getMaxDepth(): ?int public function setSerializedName(?string $serializedName /* , array $groups = ['*'] */): void { - $groups = 2 <= \func_num_args() ? func_get_arg(1) : ['*']; - - if (!$groups) { - if (isset($serializedName)) { - $this->serializedNames['*'] = $serializedName; - } else { - unset($this->serializedNames['*']); - } - } + $groups = 2 <= \func_num_args() ? (func_get_arg(1) ?: ['*']) : ['*']; if (isset($serializedName)) { foreach ($groups as $group) { @@ -157,15 +149,7 @@ public function getSerializedNames(): array public function setSerializedPath(?PropertyPath $serializedPath = null /* , array $groups = ['*'] */): void { - $groups = 2 <= \func_num_args() ? func_get_arg(1) : ['*']; - - if (!$groups) { - if (isset($serializedPath)) { - $this->serializedPaths['*'] = $serializedPath; - } else { - unset($this->serializedPaths['*']); - } - } + $groups = 2 <= \func_num_args() ? (func_get_arg(1) ?: ['*']) : ['*']; if (isset($serializedPath)) { foreach ($groups as $group) { From 8492935661727037d0e1506bf8b6ff032f046fe2 Mon Sep 17 00:00:00 2001 From: Sergey Danilchenko Date: Thu, 19 Sep 2024 15:01:45 +0200 Subject: [PATCH 11/19] Name converter: add default groups to context during normalization --- .../Serializer/NameConverter/MetadataAwareNameConverter.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Symfony/Component/Serializer/NameConverter/MetadataAwareNameConverter.php b/src/Symfony/Component/Serializer/NameConverter/MetadataAwareNameConverter.php index aaddc9b2d2552..3fbd4b9c4d9bc 100644 --- a/src/Symfony/Component/Serializer/NameConverter/MetadataAwareNameConverter.php +++ b/src/Symfony/Component/Serializer/NameConverter/MetadataAwareNameConverter.php @@ -81,6 +81,7 @@ private function getCacheValueForNormalization(string $propertyName, string $cla } $contextGroups = (array) ($context[AbstractNormalizer::GROUPS] ?? []); + $contextGroups = array_merge($contextGroups, ['Default', (false !== $nsSep = strrpos($class, '\\')) ? substr($class, $nsSep + 1) : $class]); if (null !== $attributesMetadata[$propertyName]->getSerializedName($contextGroups) && null !== $attributesMetadata[$propertyName]->getSerializedPath($contextGroups)) { throw new LogicException(\sprintf('Found SerializedName and SerializedPath attributes on property "%s" of class "%s".', $propertyName, $class)); From db5cc117bcd58c72bee3e44e33a89cb554567f46 Mon Sep 17 00:00:00 2001 From: Sergey Danilchenko Date: Fri, 20 Sep 2024 10:05:41 +0200 Subject: [PATCH 12/19] CS fix --- .../Component/Serializer/Attribute/SerializedName.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Serializer/Attribute/SerializedName.php b/src/Symfony/Component/Serializer/Attribute/SerializedName.php index 195882024e435..45ee6a146d6eb 100644 --- a/src/Symfony/Component/Serializer/Attribute/SerializedName.php +++ b/src/Symfony/Component/Serializer/Attribute/SerializedName.php @@ -25,8 +25,10 @@ class SerializedName * @param string $serializedName The name of the property as it will be serialized * @param string|string[] $groups The groups to use when serializing or deserializing */ - public function __construct(private readonly string $serializedName, string|array $groups = ['*']) - { + public function __construct( + private readonly string $serializedName, + string|array $groups = ['*'], + ) { if ('' === $serializedName) { throw new InvalidArgumentException(\sprintf('Parameter given to "%s" must be a non-empty string.', self::class)); } From d2d075e72bf0cb90256c03b33176763e123fbeb9 Mon Sep 17 00:00:00 2001 From: Sergey Danilchenko Date: Fri, 20 Sep 2024 10:07:54 +0200 Subject: [PATCH 13/19] Update CHANGELOG.md --- src/Symfony/Component/Serializer/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Serializer/CHANGELOG.md b/src/Symfony/Component/Serializer/CHANGELOG.md index b2f20facd104d..57e5f4bfb5148 100644 --- a/src/Symfony/Component/Serializer/CHANGELOG.md +++ b/src/Symfony/Component/Serializer/CHANGELOG.md @@ -8,8 +8,8 @@ CHANGELOG * Deprecate `CsvEncoderContextBuilder::withEscapeChar()` method * Add `SnakeCaseToCamelCaseNameConverter` * Support subclasses of `\DateTime` and `\DateTimeImmutable` for denormalization - * Support serialized names and paths configuration per group * Add the `UidNormalizer::NORMALIZATION_FORMAT_RFC9562` constant + * Support serialized names and paths configuration per group 7.1 --- From 4a044f060d4bf283163705d0940ba23edba388c4 Mon Sep 17 00:00:00 2001 From: Sergey Danilchenko Date: Fri, 20 Sep 2024 10:21:47 +0200 Subject: [PATCH 14/19] Update tests --- .../Tests/Mapping/Loader/AttributeLoaderTest.php | 12 ++++++------ .../Tests/Mapping/Loader/XmlFileLoaderTest.php | 12 ++++++------ .../Tests/Mapping/Loader/YamlFileLoaderTest.php | 12 ++++++------ .../NameConverter/MetadataAwareNameConverterTest.php | 8 ++++---- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/AttributeLoaderTest.php b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/AttributeLoaderTest.php index 5247b21903afc..ea252dbc47abf 100644 --- a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/AttributeLoaderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/AttributeLoaderTest.php @@ -110,8 +110,8 @@ public function testLoadSerializedName() $attributesMetadata = $classMetadata->getAttributesMetadata(); $this->assertEquals('baz', $attributesMetadata['foo']->getSerializedName()); $this->assertEquals('qux', $attributesMetadata['bar']->getSerializedName()); - $this->assertEquals('duxi', $attributesMetadata['duux']->getSerializedName()); - $this->assertEquals('duxa', $attributesMetadata['duux']->getSerializedName(['a'])); + $this->assertSame('duxi', $attributesMetadata['duux']->getSerializedName()); + $this->assertSame('duxa', $attributesMetadata['duux']->getSerializedName(['a'])); } public function testLoadSerializedPath() @@ -122,8 +122,8 @@ public function testLoadSerializedPath() $attributesMetadata = $classMetadata->getAttributesMetadata(); $this->assertEquals(new PropertyPath('[one][two]'), $attributesMetadata['three']->getSerializedPath()); $this->assertEquals(new PropertyPath('[three][four]'), $attributesMetadata['seven']->getSerializedPath()); - $this->assertEquals(new PropertyPath('[five][six]'), $attributesMetadata['eleven']->getSerializedPath()); - $this->assertEquals(new PropertyPath('[six][five]'), $attributesMetadata['eleven']->getSerializedPath(['a'])); + $this->assertSame(new PropertyPath('[five][six]'), $attributesMetadata['eleven']->getSerializedPath()); + $this->assertSame(new PropertyPath('[six][five]'), $attributesMetadata['eleven']->getSerializedPath(['a'])); } public function testLoadSerializedPathInConstructor() @@ -133,8 +133,8 @@ public function testLoadSerializedPathInConstructor() $attributesMetadata = $classMetadata->getAttributesMetadata(); $this->assertEquals(new PropertyPath('[one][two]'), $attributesMetadata['three']->getSerializedPath()); - $this->assertEquals(new PropertyPath('[five][six]'), $attributesMetadata['eleven']->getSerializedPath()); - $this->assertEquals(new PropertyPath('[six][five]'), $attributesMetadata['eleven']->getSerializedPath(['a'])); + $this->assertSame(new PropertyPath('[five][six]'), $attributesMetadata['eleven']->getSerializedPath()); + $this->assertSame(new PropertyPath('[six][five]'), $attributesMetadata['eleven']->getSerializedPath(['a'])); } public function testLoadClassMetadataAndMerge() diff --git a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/XmlFileLoaderTest.php index 9f3e2ca0beef1..357596c09e2b3 100644 --- a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/XmlFileLoaderTest.php @@ -80,8 +80,8 @@ public function testSerializedName() $attributesMetadata = $classMetadata->getAttributesMetadata(); $this->assertEquals('baz', $attributesMetadata['foo']->getSerializedName()); $this->assertEquals('qux', $attributesMetadata['bar']->getSerializedName()); - $this->assertEquals('duxi', $attributesMetadata['duux']->getSerializedName()); - $this->assertEquals('duxa', $attributesMetadata['duux']->getSerializedName(['a'])); + $this->assertSame('duxi', $attributesMetadata['duux']->getSerializedName()); + $this->assertSame('duxa', $attributesMetadata['duux']->getSerializedName(['a'])); } public function testSerializedPath() @@ -92,8 +92,8 @@ public function testSerializedPath() $attributesMetadata = $classMetadata->getAttributesMetadata(); $this->assertEquals('[one][two]', $attributesMetadata['three']->getSerializedPath()); $this->assertEquals('[three][four]', $attributesMetadata['seven']->getSerializedPath()); - $this->assertEquals('[five][six]', $attributesMetadata['eleven']->getSerializedPath()); - $this->assertEquals('[six][five]', $attributesMetadata['eleven']->getSerializedPath(['a'])); + $this->assertSame('[five][six]', $attributesMetadata['eleven']->getSerializedPath()); + $this->assertSame('[six][five]', $attributesMetadata['eleven']->getSerializedPath(['a'])); } public function testSerializedPathInConstructor() @@ -103,8 +103,8 @@ public function testSerializedPathInConstructor() $attributesMetadata = $classMetadata->getAttributesMetadata(); $this->assertEquals('[one][two]', $attributesMetadata['three']->getSerializedPath()); - $this->assertEquals('[five][six]', $attributesMetadata['eleven']->getSerializedPath()); - $this->assertEquals('[six][five]', $attributesMetadata['eleven']->getSerializedPath(['a'])); + $this->assertSame('[five][six]', $attributesMetadata['eleven']->getSerializedPath()); + $this->assertSame('[six][five]', $attributesMetadata['eleven']->getSerializedPath(['a'])); } public function testLoadDiscriminatorMap() diff --git a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/YamlFileLoaderTest.php index 7bb18a10d4391..524013b7cbd66 100644 --- a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/YamlFileLoaderTest.php @@ -97,8 +97,8 @@ public function testSerializedName() $attributesMetadata = $classMetadata->getAttributesMetadata(); $this->assertEquals('baz', $attributesMetadata['foo']->getSerializedName()); $this->assertEquals('qux', $attributesMetadata['bar']->getSerializedName()); - $this->assertEquals('duxi', $attributesMetadata['duux']->getSerializedName()); - $this->assertEquals('duxa', $attributesMetadata['duux']->getSerializedName(['a'])); + $this->assertSame('duxi', $attributesMetadata['duux']->getSerializedName()); + $this->assertSame('duxa', $attributesMetadata['duux']->getSerializedName(['a'])); } public function testSerializedPath() @@ -109,8 +109,8 @@ public function testSerializedPath() $attributesMetadata = $classMetadata->getAttributesMetadata(); $this->assertEquals(new PropertyPath('[one][two]'), $attributesMetadata['three']->getSerializedPath()); $this->assertEquals(new PropertyPath('[three][four]'), $attributesMetadata['seven']->getSerializedPath()); - $this->assertEquals(new PropertyPath('[five][six]'), $attributesMetadata['eleven']->getSerializedPath()); - $this->assertEquals(new PropertyPath('[six][five]'), $attributesMetadata['eleven']->getSerializedPath(['a'])); + $this->assertSame(new PropertyPath('[five][six]'), $attributesMetadata['eleven']->getSerializedPath()); + $this->assertSame(new PropertyPath('[six][five]'), $attributesMetadata['eleven']->getSerializedPath(['a'])); } public function testSerializedPathInConstructor() @@ -120,8 +120,8 @@ public function testSerializedPathInConstructor() $attributesMetadata = $classMetadata->getAttributesMetadata(); $this->assertEquals(new PropertyPath('[one][two]'), $attributesMetadata['three']->getSerializedPath()); - $this->assertEquals(new PropertyPath('[five][six]'), $attributesMetadata['eleven']->getSerializedPath()); - $this->assertEquals(new PropertyPath('[six][five]'), $attributesMetadata['eleven']->getSerializedPath(['a'])); + $this->assertSame(new PropertyPath('[five][six]'), $attributesMetadata['eleven']->getSerializedPath()); + $this->assertSame(new PropertyPath('[six][five]'), $attributesMetadata['eleven']->getSerializedPath(['a'])); } public function testLoadDiscriminatorMap() diff --git a/src/Symfony/Component/Serializer/Tests/NameConverter/MetadataAwareNameConverterTest.php b/src/Symfony/Component/Serializer/Tests/NameConverter/MetadataAwareNameConverterTest.php index 78fc2ed536e3f..0273fdd94a774 100644 --- a/src/Symfony/Component/Serializer/Tests/NameConverter/MetadataAwareNameConverterTest.php +++ b/src/Symfony/Component/Serializer/Tests/NameConverter/MetadataAwareNameConverterTest.php @@ -145,7 +145,7 @@ public function testNormalizeWithGroupsAndFallback(string $propertyName, string $nameConverter = new MetadataAwareNameConverter($classMetadataFactory, $fallback); - $this->assertEquals($expected, $nameConverter->normalize($propertyName, OtherSerializedNameDummy::class, null, $context)); + $this->assertSame($expected, $nameConverter->normalize($propertyName, OtherSerializedNameDummy::class, null, $context)); } /** @@ -175,7 +175,7 @@ public function testDenormalizeWithGroupsAndFallback(string $expected, string $p $nameConverter = new MetadataAwareNameConverter($classMetadataFactory, $fallback); - $this->assertEquals($expected, $nameConverter->denormalize($propertyName, OtherSerializedNameDummy::class, null, $context)); + $this->assertSame($expected, $nameConverter->denormalize($propertyName, OtherSerializedNameDummy::class, null, $context)); } public static function attributeAndContextProvider(): array @@ -255,7 +255,7 @@ public function testDenormalizeWithNestedPathAndNameDifferentGroups() { $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $nameConverter = new MetadataAwareNameConverter($classMetadataFactory); - $this->assertEquals('baz', $nameConverter->denormalize('eleven', NestedPathAndNameDifferentGroups::class, null, ['groups' => 'a'])); + $this->assertSame('baz', $nameConverter->denormalize('eleven', NestedPathAndNameDifferentGroups::class, null, ['groups' => 'a'])); } public function testNormalizeWithNestedPathAndName() @@ -280,7 +280,7 @@ public function testNormalizeWithNestedPathAndNameDifferentGroups() { $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); $nameConverter = new MetadataAwareNameConverter($classMetadataFactory); - $this->assertEquals('eleven', $nameConverter->normalize('baz', NestedPathAndNameDifferentGroups::class, null, ['groups' => 'a'])); + $this->assertSame('eleven', $nameConverter->normalize('baz', NestedPathAndNameDifferentGroups::class, null, ['groups' => 'a'])); } } From 3b3c2ba49c818a1dbbcfb47958f373d261a7a194 Mon Sep 17 00:00:00 2001 From: Sergey Danilchenko Date: Tue, 5 Nov 2024 13:10:44 +0200 Subject: [PATCH 15/19] Minor changes --- .../Component/Serializer/Mapping/AttributeMetadata.php | 6 ++++++ .../Serializer/Mapping/AttributeMetadataInterface.php | 4 ++-- .../Component/Serializer/Mapping/Loader/AttributeLoader.php | 4 ++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php b/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php index 56404d3a7b6f2..a39ab2dac56ea 100644 --- a/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php +++ b/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php @@ -273,6 +273,9 @@ public function merge(AttributeMetadataInterface $attributeMetadata): void } /** + * BC layer for extraction of serialized names from attribute metadata. + * Can be removed as soon as AttributeMetadataInterface::getSerializedNames() become part of the interface. + * * @internal * * @return array @@ -291,6 +294,9 @@ public static function getSerializedNamesFromAttributeMetadata(AttributeMetadata } /** + * BC layer for extraction of serialized paths from attribute metadata. + * Can be removed as soon as AttributeMetadataInterface::getSerializedPaths() become part of the interface. + * * @internal * * @return array diff --git a/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php b/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php index aa5e7d55afab3..bc8c08af2bcbf 100644 --- a/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php +++ b/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php @@ -22,8 +22,8 @@ * * @author Kévin Dunglas * - * @method string[] getSerializedNames() Gets all the serialized names per group ("*" being the base name applied to all groups). - * @method PropertyPath[] getSerializedPaths() Gets all the serialized paths per group ("*" being the base path applied to all groups). + * @method string[] getSerializedNames() Gets all the serialized names per group. + * @method PropertyPath[] getSerializedPaths() Gets all the serialized paths per group. */ interface AttributeMetadataInterface { diff --git a/src/Symfony/Component/Serializer/Mapping/Loader/AttributeLoader.php b/src/Symfony/Component/Serializer/Mapping/Loader/AttributeLoader.php index 4d674fb8cbec5..7b5f1c4e031c3 100644 --- a/src/Symfony/Component/Serializer/Mapping/Loader/AttributeLoader.php +++ b/src/Symfony/Component/Serializer/Mapping/Loader/AttributeLoader.php @@ -95,8 +95,8 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool match (true) { $attribute instanceof MaxDepth => $attributeMetadata->setMaxDepth($attribute->getMaxDepth()), - $attribute instanceof SerializedName => $this->setAttributeSerializedNameForGroups($attribute, $attributesMetadata[$property->name]), - $attribute instanceof SerializedPath => $this->setAttributeSerializedPathForGroups($attribute, $attributesMetadata[$property->name]), + $attribute instanceof SerializedName => $this->setAttributeSerializedNameForGroups($attribute, $attributeMetadata), + $attribute instanceof SerializedPath => $this->setAttributeSerializedPathForGroups($attribute, $attributeMetadata), $attribute instanceof Ignore => $attributeMetadata->setIgnore(true), $attribute instanceof Context => $this->setAttributeContextsForGroups($attribute, $attributeMetadata), default => null, From 719120ad8bf1c97f2352e841f6d0ce66de1f8324 Mon Sep 17 00:00:00 2001 From: Sergey Danilchenko Date: Tue, 5 Nov 2024 17:00:04 +0200 Subject: [PATCH 16/19] Do not fall back to '*' group in serialized name/path attributes --- src/Symfony/Component/Serializer/Attribute/SerializedName.php | 2 +- src/Symfony/Component/Serializer/Attribute/SerializedPath.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Serializer/Attribute/SerializedName.php b/src/Symfony/Component/Serializer/Attribute/SerializedName.php index 45ee6a146d6eb..4c75f5c133680 100644 --- a/src/Symfony/Component/Serializer/Attribute/SerializedName.php +++ b/src/Symfony/Component/Serializer/Attribute/SerializedName.php @@ -33,7 +33,7 @@ public function __construct( throw new InvalidArgumentException(\sprintf('Parameter given to "%s" must be a non-empty string.', self::class)); } - $this->groups = ((array) $groups) ?: ['*']; + $this->groups = (array) $groups; foreach ($this->groups as $group) { if (!\is_string($group)) { diff --git a/src/Symfony/Component/Serializer/Attribute/SerializedPath.php b/src/Symfony/Component/Serializer/Attribute/SerializedPath.php index 9c3910754edbd..0b12c69291522 100644 --- a/src/Symfony/Component/Serializer/Attribute/SerializedPath.php +++ b/src/Symfony/Component/Serializer/Attribute/SerializedPath.php @@ -37,7 +37,7 @@ public function __construct(string $serializedPath, string|array $groups = ['*'] throw new InvalidArgumentException(\sprintf('Parameter given to "%s" must be a valid property path.', self::class)); } - $this->groups = ((array) $groups) ?: ['*']; + $this->groups = (array) $groups; foreach ($this->groups as $group) { if (!\is_string($group)) { From d4bcf650fff50fa4eb0f9af6950a6317f8e00050 Mon Sep 17 00:00:00 2001 From: Sergey Danilchenko Date: Tue, 5 Nov 2024 17:12:16 +0200 Subject: [PATCH 17/19] Change way of retrieving BC-friendly arguments --- .../Component/Serializer/Mapping/AttributeMetadata.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php b/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php index a39ab2dac56ea..c5e9bf9475613 100644 --- a/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php +++ b/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php @@ -116,7 +116,7 @@ public function getMaxDepth(): ?int public function setSerializedName(?string $serializedName /* , array $groups = ['*'] */): void { - $groups = 2 <= \func_num_args() ? (func_get_arg(1) ?: ['*']) : ['*']; + $groups = \func_get_args()[1] ?? ['*']; if (isset($serializedName)) { foreach ($groups as $group) { @@ -131,7 +131,7 @@ public function setSerializedName(?string $serializedName /* , array $groups = [ public function getSerializedName(/* array $groups = ['*'] */): ?string { - $groups = 1 <= \func_num_args() ? func_get_arg(0) : ['*']; + $groups = \func_get_args()[0] ?? ['*']; foreach ($groups as $group) { if (isset($this->serializedNames[$group])) { @@ -149,7 +149,7 @@ public function getSerializedNames(): array public function setSerializedPath(?PropertyPath $serializedPath = null /* , array $groups = ['*'] */): void { - $groups = 2 <= \func_num_args() ? (func_get_arg(1) ?: ['*']) : ['*']; + $groups = \func_get_args()[1] ?? ['*']; if (isset($serializedPath)) { foreach ($groups as $group) { @@ -164,7 +164,7 @@ public function setSerializedPath(?PropertyPath $serializedPath = null /* , arra public function getSerializedPath(/* array $groups = ['*'] */): ?PropertyPath { - $groups = 1 <= \func_num_args() ? func_get_arg(0) : ['*']; + $groups = \func_get_args()[0] ?? ['*']; foreach ($groups as $group) { if (isset($this->serializedPaths[$group])) { From 3fbe2c6f2bb75f97dc42beec8b27573cab194639 Mon Sep 17 00:00:00 2001 From: Sergey Danilchenko Date: Mon, 11 Nov 2024 14:55:32 +0200 Subject: [PATCH 18/19] Partial revert due to failing tests --- .../Tests/Mapping/Loader/AttributeLoaderTest.php | 12 ++++++------ .../Tests/Mapping/Loader/XmlFileLoaderTest.php | 12 ++++++------ .../Tests/Mapping/Loader/YamlFileLoaderTest.php | 12 ++++++------ 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/AttributeLoaderTest.php b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/AttributeLoaderTest.php index 53131ed7028b5..80753eec8fb90 100644 --- a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/AttributeLoaderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/AttributeLoaderTest.php @@ -111,8 +111,8 @@ public function testLoadSerializedName() $attributesMetadata = $classMetadata->getAttributesMetadata(); $this->assertEquals('baz', $attributesMetadata['foo']->getSerializedName()); $this->assertEquals('qux', $attributesMetadata['bar']->getSerializedName()); - $this->assertSame('duxi', $attributesMetadata['duux']->getSerializedName()); - $this->assertSame('duxa', $attributesMetadata['duux']->getSerializedName(['a'])); + $this->assertEquals('duxi', $attributesMetadata['duux']->getSerializedName()); + $this->assertEquals('duxa', $attributesMetadata['duux']->getSerializedName(['a'])); } public function testLoadSerializedPath() @@ -123,8 +123,8 @@ public function testLoadSerializedPath() $attributesMetadata = $classMetadata->getAttributesMetadata(); $this->assertEquals(new PropertyPath('[one][two]'), $attributesMetadata['three']->getSerializedPath()); $this->assertEquals(new PropertyPath('[three][four]'), $attributesMetadata['seven']->getSerializedPath()); - $this->assertSame(new PropertyPath('[five][six]'), $attributesMetadata['eleven']->getSerializedPath()); - $this->assertSame(new PropertyPath('[six][five]'), $attributesMetadata['eleven']->getSerializedPath(['a'])); + $this->assertEquals(new PropertyPath('[five][six]'), $attributesMetadata['eleven']->getSerializedPath()); + $this->assertEquals(new PropertyPath('[six][five]'), $attributesMetadata['eleven']->getSerializedPath(['a'])); } public function testLoadSerializedPathInConstructor() @@ -134,8 +134,8 @@ public function testLoadSerializedPathInConstructor() $attributesMetadata = $classMetadata->getAttributesMetadata(); $this->assertEquals(new PropertyPath('[one][two]'), $attributesMetadata['three']->getSerializedPath()); - $this->assertSame(new PropertyPath('[five][six]'), $attributesMetadata['eleven']->getSerializedPath()); - $this->assertSame(new PropertyPath('[six][five]'), $attributesMetadata['eleven']->getSerializedPath(['a'])); + $this->assertEquals(new PropertyPath('[five][six]'), $attributesMetadata['eleven']->getSerializedPath()); + $this->assertEquals(new PropertyPath('[six][five]'), $attributesMetadata['eleven']->getSerializedPath(['a'])); } public function testLoadClassMetadataAndMerge() diff --git a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/XmlFileLoaderTest.php index 357596c09e2b3..9f3e2ca0beef1 100644 --- a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/XmlFileLoaderTest.php @@ -80,8 +80,8 @@ public function testSerializedName() $attributesMetadata = $classMetadata->getAttributesMetadata(); $this->assertEquals('baz', $attributesMetadata['foo']->getSerializedName()); $this->assertEquals('qux', $attributesMetadata['bar']->getSerializedName()); - $this->assertSame('duxi', $attributesMetadata['duux']->getSerializedName()); - $this->assertSame('duxa', $attributesMetadata['duux']->getSerializedName(['a'])); + $this->assertEquals('duxi', $attributesMetadata['duux']->getSerializedName()); + $this->assertEquals('duxa', $attributesMetadata['duux']->getSerializedName(['a'])); } public function testSerializedPath() @@ -92,8 +92,8 @@ public function testSerializedPath() $attributesMetadata = $classMetadata->getAttributesMetadata(); $this->assertEquals('[one][two]', $attributesMetadata['three']->getSerializedPath()); $this->assertEquals('[three][four]', $attributesMetadata['seven']->getSerializedPath()); - $this->assertSame('[five][six]', $attributesMetadata['eleven']->getSerializedPath()); - $this->assertSame('[six][five]', $attributesMetadata['eleven']->getSerializedPath(['a'])); + $this->assertEquals('[five][six]', $attributesMetadata['eleven']->getSerializedPath()); + $this->assertEquals('[six][five]', $attributesMetadata['eleven']->getSerializedPath(['a'])); } public function testSerializedPathInConstructor() @@ -103,8 +103,8 @@ public function testSerializedPathInConstructor() $attributesMetadata = $classMetadata->getAttributesMetadata(); $this->assertEquals('[one][two]', $attributesMetadata['three']->getSerializedPath()); - $this->assertSame('[five][six]', $attributesMetadata['eleven']->getSerializedPath()); - $this->assertSame('[six][five]', $attributesMetadata['eleven']->getSerializedPath(['a'])); + $this->assertEquals('[five][six]', $attributesMetadata['eleven']->getSerializedPath()); + $this->assertEquals('[six][five]', $attributesMetadata['eleven']->getSerializedPath(['a'])); } public function testLoadDiscriminatorMap() diff --git a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/YamlFileLoaderTest.php index 524013b7cbd66..7bb18a10d4391 100644 --- a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/YamlFileLoaderTest.php @@ -97,8 +97,8 @@ public function testSerializedName() $attributesMetadata = $classMetadata->getAttributesMetadata(); $this->assertEquals('baz', $attributesMetadata['foo']->getSerializedName()); $this->assertEquals('qux', $attributesMetadata['bar']->getSerializedName()); - $this->assertSame('duxi', $attributesMetadata['duux']->getSerializedName()); - $this->assertSame('duxa', $attributesMetadata['duux']->getSerializedName(['a'])); + $this->assertEquals('duxi', $attributesMetadata['duux']->getSerializedName()); + $this->assertEquals('duxa', $attributesMetadata['duux']->getSerializedName(['a'])); } public function testSerializedPath() @@ -109,8 +109,8 @@ public function testSerializedPath() $attributesMetadata = $classMetadata->getAttributesMetadata(); $this->assertEquals(new PropertyPath('[one][two]'), $attributesMetadata['three']->getSerializedPath()); $this->assertEquals(new PropertyPath('[three][four]'), $attributesMetadata['seven']->getSerializedPath()); - $this->assertSame(new PropertyPath('[five][six]'), $attributesMetadata['eleven']->getSerializedPath()); - $this->assertSame(new PropertyPath('[six][five]'), $attributesMetadata['eleven']->getSerializedPath(['a'])); + $this->assertEquals(new PropertyPath('[five][six]'), $attributesMetadata['eleven']->getSerializedPath()); + $this->assertEquals(new PropertyPath('[six][five]'), $attributesMetadata['eleven']->getSerializedPath(['a'])); } public function testSerializedPathInConstructor() @@ -120,8 +120,8 @@ public function testSerializedPathInConstructor() $attributesMetadata = $classMetadata->getAttributesMetadata(); $this->assertEquals(new PropertyPath('[one][two]'), $attributesMetadata['three']->getSerializedPath()); - $this->assertSame(new PropertyPath('[five][six]'), $attributesMetadata['eleven']->getSerializedPath()); - $this->assertSame(new PropertyPath('[six][five]'), $attributesMetadata['eleven']->getSerializedPath(['a'])); + $this->assertEquals(new PropertyPath('[five][six]'), $attributesMetadata['eleven']->getSerializedPath()); + $this->assertEquals(new PropertyPath('[six][five]'), $attributesMetadata['eleven']->getSerializedPath(['a'])); } public function testLoadDiscriminatorMap() From b3f789f38b4b07c1c79114f88950686291f27c8f Mon Sep 17 00:00:00 2001 From: Sergey Danilchenko Date: Mon, 11 Nov 2024 15:01:43 +0200 Subject: [PATCH 19/19] Partial revert due to failing tests --- .../Component/Serializer/Attribute/SerializedName.php | 2 +- .../Component/Serializer/Attribute/SerializedPath.php | 2 +- .../Component/Serializer/Mapping/AttributeMetadata.php | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Symfony/Component/Serializer/Attribute/SerializedName.php b/src/Symfony/Component/Serializer/Attribute/SerializedName.php index 4c75f5c133680..45ee6a146d6eb 100644 --- a/src/Symfony/Component/Serializer/Attribute/SerializedName.php +++ b/src/Symfony/Component/Serializer/Attribute/SerializedName.php @@ -33,7 +33,7 @@ public function __construct( throw new InvalidArgumentException(\sprintf('Parameter given to "%s" must be a non-empty string.', self::class)); } - $this->groups = (array) $groups; + $this->groups = ((array) $groups) ?: ['*']; foreach ($this->groups as $group) { if (!\is_string($group)) { diff --git a/src/Symfony/Component/Serializer/Attribute/SerializedPath.php b/src/Symfony/Component/Serializer/Attribute/SerializedPath.php index 0b12c69291522..9c3910754edbd 100644 --- a/src/Symfony/Component/Serializer/Attribute/SerializedPath.php +++ b/src/Symfony/Component/Serializer/Attribute/SerializedPath.php @@ -37,7 +37,7 @@ public function __construct(string $serializedPath, string|array $groups = ['*'] throw new InvalidArgumentException(\sprintf('Parameter given to "%s" must be a valid property path.', self::class)); } - $this->groups = (array) $groups; + $this->groups = ((array) $groups) ?: ['*']; foreach ($this->groups as $group) { if (!\is_string($group)) { diff --git a/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php b/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php index c5e9bf9475613..a39ab2dac56ea 100644 --- a/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php +++ b/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php @@ -116,7 +116,7 @@ public function getMaxDepth(): ?int public function setSerializedName(?string $serializedName /* , array $groups = ['*'] */): void { - $groups = \func_get_args()[1] ?? ['*']; + $groups = 2 <= \func_num_args() ? (func_get_arg(1) ?: ['*']) : ['*']; if (isset($serializedName)) { foreach ($groups as $group) { @@ -131,7 +131,7 @@ public function setSerializedName(?string $serializedName /* , array $groups = [ public function getSerializedName(/* array $groups = ['*'] */): ?string { - $groups = \func_get_args()[0] ?? ['*']; + $groups = 1 <= \func_num_args() ? func_get_arg(0) : ['*']; foreach ($groups as $group) { if (isset($this->serializedNames[$group])) { @@ -149,7 +149,7 @@ public function getSerializedNames(): array public function setSerializedPath(?PropertyPath $serializedPath = null /* , array $groups = ['*'] */): void { - $groups = \func_get_args()[1] ?? ['*']; + $groups = 2 <= \func_num_args() ? (func_get_arg(1) ?: ['*']) : ['*']; if (isset($serializedPath)) { foreach ($groups as $group) { @@ -164,7 +164,7 @@ public function setSerializedPath(?PropertyPath $serializedPath = null /* , arra public function getSerializedPath(/* array $groups = ['*'] */): ?PropertyPath { - $groups = \func_get_args()[0] ?? ['*']; + $groups = 1 <= \func_num_args() ? func_get_arg(0) : ['*']; foreach ($groups as $group) { if (isset($this->serializedPaths[$group])) { pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy