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

Skip to content

Commit 0b35462

Browse files
committed
[Serializer] Use version constraints attribute on normalize/denormalize
1 parent 11b7b60 commit 0b35462

File tree

3 files changed

+154
-2
lines changed

3 files changed

+154
-2
lines changed

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,4 +552,16 @@ protected function getAttributeMetadata(object|string $objectOrClass, string $at
552552

553553
return $this->classMetadataFactory->getMetadataFor($objectOrClass)->getAttributesMetadata()[$attribute] ?? null;
554554
}
555+
556+
/**
557+
* @param array<string, AttributeMetadataInterface> $attributesMetadata
558+
*/
559+
protected function isVersionCompatible(string $version, array $attributesMetadata, string $attributeName): bool
560+
{
561+
if (!isset($attributesMetadata[$attributeName])) {
562+
return true;
563+
}
564+
565+
return $attributesMetadata[$attributeName]->isVersionCompatible($version);
566+
}
555567
}

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

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,6 @@ public function __construct(
138138
}
139139

140140
/**
141-
* @param array $context
142-
*
143141
* @return bool
144142
*/
145143
public function supportsNormalization(mixed $data, string $format = null /* , array $context = [] */)
@@ -206,6 +204,11 @@ public function normalize(mixed $object, string $format = null, array $context =
206204
$stack[$attribute] = $this->applyCallbacks($attributeValue, $object, $attribute, $format, $attributeContext);
207205
}
208206

207+
$objectVersion = $this->getObjectVersion($object, $stack);
208+
if (null !== $objectVersion) {
209+
$stack = $this->filterAttributesByVersion($objectVersion, $attributesMetadata, $stack);
210+
}
211+
209212
foreach ($stack as $attribute => $attributeValue) {
210213
$attributeContext = $this->getAttributeNormalizationContext($object, $attribute, $context);
211214

@@ -320,7 +323,9 @@ public function denormalize(mixed $data, string $type, string $format = null, ar
320323
}
321324

322325
$allowedAttributes = $this->getAllowedAttributes($type, $context, true);
326+
$versionedConstraintAttributeCallables = $this->getVersionedConstraintAttributeCallables($type);
323327
$normalizedData = $this->prepareForDenormalization($data);
328+
324329
$extraAttributes = [];
325330

326331
$mappedClass = $this->getMappedClass($normalizedData, $type, $context);
@@ -336,6 +341,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar
336341
$normalizedData = $this->removeNestedValue($serializedPath->getElements(), $normalizedData);
337342
}
338343

344+
$objectVersion = $this->getObjectVersion($type, $normalizedData);
339345
$normalizedData = array_merge($normalizedData, $nestedData);
340346

341347
$object = $this->instantiateObject($normalizedData, $mappedClass, $context, new \ReflectionClass($mappedClass), $allowedAttributes, $format);
@@ -382,6 +388,9 @@ public function denormalize(mixed $data, string $type, string $format = null, ar
382388
}
383389

384390
$value = $this->applyCallbacks($value, $resolvedClass, $attribute, $format, $attributeContext);
391+
if (null !== $objectVersion) {
392+
$value = $this->getValueByVersion($value, $objectVersion, $attribute, $versionedConstraintAttributeCallables);
393+
}
385394

386395
try {
387396
$this->setAttributeValue($object, $attribute, $value, $format, $attributeContext);
@@ -594,6 +603,21 @@ private function validateAndDenormalize(array $types, string $currentClass, stri
594603
throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be one of "%s" ("%s" given).', $attribute, $currentClass, implode('", "', array_keys($expectedTypes)), get_debug_type($data)), $data, array_keys($expectedTypes), $context['deserialization_path'] ?? $attribute);
595604
}
596605

606+
private function getObjectVersion(object|string $objectOrClass, array $stack): ?string
607+
{
608+
$objectVersion = null;
609+
foreach ($this->classMetadataFactory?->getMetadataFor($objectOrClass)->getAttributesMetadata() ?? [] as $attribute => $attributeMetadata) {
610+
if ($attributeMetadata->isVersion()) {
611+
if (null !== $objectVersion) {
612+
throw new LogicException(sprintf('Too many version ["%s","%s] attributes class "%s".', $attribute, $objectVersion, \is_object($objectOrClass) ? $objectOrClass::class : $objectOrClass));
613+
}
614+
$objectVersion = $attribute;
615+
}
616+
}
617+
618+
return $objectVersion ? $stack[$objectVersion] : null;
619+
}
620+
597621
/**
598622
* @internal
599623
*/
@@ -812,4 +836,43 @@ private function getMappedClass(array $data, string $class, array $context): str
812836

813837
return $mappedClass;
814838
}
839+
840+
/**
841+
* @param array<string, AttributeMetadataInterface> $attributesMetadata
842+
*/
843+
private function filterAttributesByVersion(string $objectVersion, array $attributesMetadata, array $stack): array
844+
{
845+
return array_filter(
846+
$stack,
847+
fn (string $key) => $this->isVersionCompatible($objectVersion, $attributesMetadata, $key),
848+
\ARRAY_FILTER_USE_KEY
849+
);
850+
}
851+
852+
/**
853+
* @param array<string, callable> $callables
854+
*/
855+
private function getValueByVersion(mixed $value, string $objectVersion, string $attribute, array $callables): mixed
856+
{
857+
if (!isset($callables[$attribute])) {
858+
return $value;
859+
}
860+
861+
return $callables[$attribute]($objectVersion) ? $value : null;
862+
}
863+
864+
/**
865+
* @param class-string $class
866+
*
867+
* @return array<class-string, Closure(string)>
868+
*/
869+
private function getVersionedConstraintAttributeCallables(string $class): array
870+
{
871+
$attributes = [];
872+
foreach ($this->classMetadataFactory?->getMetadataFor($class)?->getAttributesMetadata() ?? [] as $attribute => $attributeMetadata) {
873+
$attributes[$attribute] = $attributeMetadata->isVersionCompatible(...);
874+
}
875+
876+
return $attributes;
877+
}
815878
}

src/Symfony/Component/Serializer/Tests/Normalizer/AbstractNormalizerTest.php

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use PHPUnit\Framework\MockObject\MockObject;
1515
use PHPUnit\Framework\TestCase;
1616
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
17+
use Symfony\Component\Serializer\Annotation\VersionConstraint;
1718
use Symfony\Component\Serializer\Encoder\JsonEncoder;
1819
use Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException;
1920
use Symfony\Component\Serializer\Mapping\AttributeMetadata;
@@ -27,6 +28,7 @@
2728
use Symfony\Component\Serializer\Serializer;
2829
use Symfony\Component\Serializer\Tests\Fixtures\AbstractNormalizerDummy;
2930
use Symfony\Component\Serializer\Tests\Fixtures\Annotations\IgnoreDummy;
31+
use Symfony\Component\Serializer\Tests\Fixtures\Annotations\VersionDummy as AnnotationsVersionDummy;
3032
use Symfony\Component\Serializer\Tests\Fixtures\Dummy;
3133
use Symfony\Component\Serializer\Tests\Fixtures\NullableConstructorArgumentDummy;
3234
use Symfony\Component\Serializer\Tests\Fixtures\NullableOptionalConstructorArgumentDummy;
@@ -266,4 +268,79 @@ public function testIgnore()
266268

267269
$this->assertSame([], $normalizer->normalize($dummy));
268270
}
271+
272+
/**
273+
* @dataProvider providesNormalizeVersionAndConstraints
274+
*/
275+
public function testNormalizeVersionConstraint(?string $version, ?string $since, ?string $until, array $expectedVersionedProperties)
276+
{
277+
$classMetadata = new ClassMetadata(AnnotationsVersionDummy::class);
278+
279+
$attributeVersionMetadata = new AttributeMetadata('objectVersion');
280+
$attributeVersionMetadata->setVersion(true);
281+
$classMetadata->addAttributeMetadata($attributeVersionMetadata);
282+
283+
$attributeVersionConstraintMetadata = new AttributeMetadata('versionedProperty');
284+
$attributeVersionConstraintMetadata->setVersionConstraint(new VersionConstraint(since: $since, until: $until));
285+
$classMetadata->addAttributeMetadata($attributeVersionConstraintMetadata);
286+
287+
$attributeVersionConstraint2Metadata = new AttributeMetadata('versionedProperty2');
288+
$attributeVersionConstraint2Metadata->setVersionConstraint(new VersionConstraint(since: $since, until: $until));
289+
$classMetadata->addAttributeMetadata($attributeVersionConstraint2Metadata);
290+
291+
$this->classMetadata->method('getMetadataFor')->willReturn($classMetadata);
292+
293+
$dummy = new AnnotationsVersionDummy();
294+
$dummy->objectVersion = $version;
295+
$dummy->versionedProperty = 'foo';
296+
$dummy->versionedProperty2 = 'foo2';
297+
298+
$normalizer = new PropertyNormalizer($this->classMetadata);
299+
300+
$this->assertSame([
301+
'foo' => null,
302+
'objectVersion' => $version,
303+
] + $expectedVersionedProperties, $normalizer->normalize($dummy));
304+
}
305+
306+
public static function providesNormalizeVersionAndConstraints(): \Generator
307+
{
308+
yield 'Version in range' => ['1.2', '1.1', '1.5', ['versionedProperty' => 'foo', 'versionedProperty2' => 'foo2']];
309+
yield 'Version out of range' => ['0.9', '1.1', '1.5', []];
310+
}
311+
312+
/**
313+
* @dataProvider providesDenormalizeVersionAndConstraints
314+
*/
315+
public function testDenormalizeVersionConstraint(?string $version, ?string $since, ?string $until, array $normalizedVersionedProperties, ?string $expectedVersionedProperty)
316+
{
317+
$classMetadata = new ClassMetadata(AnnotationsVersionDummy::class);
318+
319+
$attributeVersionMetadata = new AttributeMetadata('objectVersion');
320+
$attributeVersionMetadata->setVersion(true);
321+
$classMetadata->addAttributeMetadata($attributeVersionMetadata);
322+
323+
$attributeVersionConstraintMetadata = new AttributeMetadata('versionedProperty');
324+
$attributeVersionConstraintMetadata->setVersionConstraint(new VersionConstraint(since: $since, until: $until));
325+
$classMetadata->addAttributeMetadata($attributeVersionConstraintMetadata);
326+
327+
$this->classMetadata->method('getMetadataFor')->willReturn($classMetadata);
328+
329+
$normalized = [
330+
'foo' => null,
331+
'objectVersion' => $version,
332+
'versionedProperty' => 'foo',
333+
];
334+
335+
$normalizer = new PropertyNormalizer($this->classMetadata);
336+
337+
$denormalizedObject = $normalizer->denormalize($normalized, AnnotationsVersionDummy::class);
338+
$this->assertSame($expectedVersionedProperty, $denormalizedObject->versionedProperty);
339+
}
340+
341+
public static function providesDenormalizeVersionAndConstraints(): \Generator
342+
{
343+
yield 'Version in range' => ['1.2', '1.1', '1.5', ['versionedProperty' => 'foo'], 'foo'];
344+
yield 'Version out of range' => ['0.9', '1.1', '1.5', [], null];
345+
}
269346
}

0 commit comments

Comments
 (0)