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

Skip to content

Commit dc03515

Browse files
committed
[Serializer] Add version related attributes
1 parent 3e172b3 commit dc03515

File tree

12 files changed

+420
-2
lines changed

12 files changed

+420
-2
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Serializer\Annotation;
13+
14+
/**
15+
* Annotation class for @Version().
16+
*
17+
* @Annotation
18+
*
19+
* @NamedArgumentConstructor
20+
*
21+
* @Target({"PROPERTY", "METHOD"})
22+
*
23+
* @author Olivier Michaud <[email protected]>
24+
*/
25+
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)]
26+
final class Version
27+
{
28+
public function __construct()
29+
{
30+
}
31+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Serializer\Annotation;
13+
14+
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
15+
16+
/**
17+
* Annotation class for @VersionConstraint().
18+
*
19+
* @Annotation
20+
*
21+
* @NamedArgumentConstructor
22+
*
23+
* @Target({"PROPERTY", "METHOD"})
24+
*
25+
* @author Olivier Michaud <[email protected]>
26+
*/
27+
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)]
28+
final class VersionConstraint
29+
{
30+
public function __construct(private readonly ?string $since = null, private readonly ?string $until = null)
31+
{
32+
if ('' === $since) {
33+
throw new InvalidArgumentException(sprintf('Parameter "since" of annotation "%s" must be a non-empty string.', self::class));
34+
}
35+
if ('' === $until) {
36+
throw new InvalidArgumentException(sprintf('Parameter "until" of annotation "%s" must be a non-empty string.', self::class));
37+
}
38+
if (null === $since && null === $until) {
39+
throw new InvalidArgumentException(sprintf('At least one of "since" or "until" properties of annotation "%s" have to be defined.', self::class));
40+
}
41+
}
42+
43+
public function isVersionCompatible(string $version): bool
44+
{
45+
if ($this->since) {
46+
if (!version_compare($version, $this->since, '>=')) {
47+
return false;
48+
}
49+
}
50+
if ($this->until) {
51+
if (!version_compare($version, $this->until, '<=')) {
52+
return false;
53+
}
54+
}
55+
56+
return true;
57+
}
58+
}

src/Symfony/Component/Serializer/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
6.4
5+
---
6+
7+
* Add `Version` and `VersionConstraint` annotation/attributes to help versioning on normalize/denormalize operations
8+
49
6.3
510
---
611

src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\Serializer\Mapping;
1313

1414
use Symfony\Component\PropertyAccess\PropertyPath;
15+
use Symfony\Component\Serializer\Annotation\VersionConstraint;
1516

1617
/**
1718
* @author Kévin Dunglas <[email protected]>
@@ -78,6 +79,27 @@ class AttributeMetadata implements AttributeMetadataInterface
7879
*/
7980
public array $denormalizationContexts = [];
8081

82+
/**
83+
* @internal This property is public in order to reduce the size of the
84+
* class' serialized representation. Do not access it. Use
85+
* {@link isVersion()} instead.
86+
*/
87+
public bool $version = false;
88+
89+
/**
90+
* @internal This property is public in order to reduce the size of the
91+
* class' serialized representation. Do not access it. Use
92+
* {@link getVersionConstraint()} instead.
93+
*/
94+
public ?VersionConstraint $versionConstraint = null;
95+
96+
/**
97+
* @internal This property is public in order to reduce the size of the
98+
* class' serialized representation. Do not access it. Use
99+
* {@link isVersionCompatible()} instead.
100+
*/
101+
public ?\Closure $versionConstraintCallable = null;
102+
81103
public function __construct(string $name)
82104
{
83105
$this->name = $name;
@@ -144,6 +166,31 @@ public function isIgnored(): bool
144166
return $this->ignore;
145167
}
146168

169+
public function setVersion(bool $version): void
170+
{
171+
$this->version = $version;
172+
}
173+
174+
public function isVersion(): bool
175+
{
176+
return $this->version;
177+
}
178+
179+
public function setVersionConstraint(VersionConstraint $versionConstraint): void
180+
{
181+
$this->versionConstraint = $versionConstraint;
182+
}
183+
184+
public function getVersionConstraint(): ?VersionConstraint
185+
{
186+
return $this->versionConstraint;
187+
}
188+
189+
public function isVersionCompatible(string $version): bool
190+
{
191+
return !$this->versionConstraint || $this->versionConstraint->isVersionCompatible($version);
192+
}
193+
147194
public function getNormalizationContexts(): array
148195
{
149196
return $this->normalizationContexts;
@@ -225,6 +272,6 @@ public function merge(AttributeMetadataInterface $attributeMetadata): void
225272
*/
226273
public function __sleep(): array
227274
{
228-
return ['name', 'groups', 'maxDepth', 'serializedName', 'serializedPath', 'ignore', 'normalizationContexts', 'denormalizationContexts'];
275+
return ['name', 'groups', 'maxDepth', 'serializedName', 'serializedPath', 'ignore', 'normalizationContexts', 'denormalizationContexts', 'version', 'versionConstraint'];
229276
}
230277
}

src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\Serializer\Mapping;
1313

1414
use Symfony\Component\PropertyAccess\PropertyPath;
15+
use Symfony\Component\Serializer\Annotation\VersionConstraint;
1516

1617
/**
1718
* Stores metadata needed for serializing and deserializing attributes.
@@ -75,6 +76,20 @@ public function setIgnore(bool $ignore): void;
7576
*/
7677
public function isIgnored(): bool;
7778

79+
/**
80+
* Sets if this attribute is holding the version. Only one attribute can hold version at once.
81+
*/
82+
public function setVersion(bool $version): void;
83+
84+
/**
85+
* Gets if this attribute is holding the version.
86+
*/
87+
public function isVersion(): bool;
88+
89+
public function setVersionConstraint(VersionConstraint $versionConstraint): void;
90+
91+
public function isVersionCompatible(string $version): bool;
92+
7893
/**
7994
* Merges an {@see AttributeMetadataInterface} with in the current one.
8095
*/

src/Symfony/Component/Serializer/Mapping/Loader/AnnotationLoader.php

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
use Symfony\Component\Serializer\Annotation\MaxDepth;
2020
use Symfony\Component\Serializer\Annotation\SerializedName;
2121
use Symfony\Component\Serializer\Annotation\SerializedPath;
22+
use Symfony\Component\Serializer\Annotation\Version;
23+
use Symfony\Component\Serializer\Annotation\VersionConstraint;
24+
use Symfony\Component\Serializer\Exception\LogicException;
2225
use Symfony\Component\Serializer\Exception\MappingException;
2326
use Symfony\Component\Serializer\Mapping\AttributeMetadata;
2427
use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface;
@@ -37,6 +40,8 @@ class AnnotationLoader implements LoaderInterface
3740
DiscriminatorMap::class,
3841
Groups::class,
3942
Ignore::class,
43+
Version::class,
44+
VersionConstraint::class,
4045
MaxDepth::class,
4146
SerializedName::class,
4247
SerializedPath::class,
@@ -65,6 +70,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool
6570
}
6671
}
6772

73+
$hasVersionProperty = false;
6874
foreach ($reflectionClass->getProperties() as $property) {
6975
if (!isset($attributesMetadata[$property->name])) {
7076
$attributesMetadata[$property->name] = new AttributeMetadata($property->name);
@@ -85,6 +91,14 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool
8591
$attributesMetadata[$property->name]->setSerializedPath($annotation->getSerializedPath());
8692
} elseif ($annotation instanceof Ignore) {
8793
$attributesMetadata[$property->name]->setIgnore(true);
94+
} elseif ($annotation instanceof VersionConstraint) {
95+
$attributesMetadata[$property->name]->setVersionConstraint($annotation);
96+
} elseif ($annotation instanceof Version) {
97+
if ($hasVersionProperty) {
98+
throw new LogicException(sprintf('Version on "%s::%s()" cannot be added. Version holder property can only be set once.', $className, $property->name));
99+
}
100+
$attributesMetadata[$property->name]->setVersion(true);
101+
$hasVersionProperty = true;
88102
} elseif ($annotation instanceof Context) {
89103
$this->setAttributeContextsForGroups($annotation, $attributesMetadata[$property->name]);
90104
}
@@ -114,7 +128,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool
114128
$classMetadata->addAttributeMetadata($attributeMetadata);
115129
}
116130
}
117-
131+
$hasVersionProperty = false;
118132
foreach ($this->loadAnnotations($method) as $annotation) {
119133
if ($annotation instanceof Groups) {
120134
if (!$accessorOrMutator) {
@@ -148,6 +162,23 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool
148162
}
149163

150164
$attributeMetadata->setIgnore(true);
165+
} elseif ($annotation instanceof VersionConstraint) {
166+
if (!$accessorOrMutator) {
167+
throw new MappingException(sprintf('Ignore on "%s::%s()" cannot be added. Ignore can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name));
168+
}
169+
170+
$attributeMetadata->setVersionConstraint($annotation);
171+
} elseif ($annotation instanceof Version) {
172+
if (!$accessorOrMutator) {
173+
throw new MappingException(sprintf('Ignore on "%s::%s()" cannot be added. Ignore can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name));
174+
}
175+
176+
if ($hasVersionProperty) {
177+
throw new LogicException(sprintf('Version on "%s::%s()" cannot be added. Version holder property can only be set once.', $className, $method->name));
178+
}
179+
180+
$attributeMetadata->setVersion(true);
181+
$hasVersionProperty = true;
151182
} elseif ($annotation instanceof Context) {
152183
if (!$accessorOrMutator) {
153184
throw new MappingException(sprintf('Context on "%s::%s()" cannot be added. Context can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name));
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Annotation;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Serializer\Annotation\VersionConstraint;
16+
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
17+
18+
/**
19+
* @author Olivier Michaud <[email protected]>
20+
*/
21+
class VersionConstraintTest extends TestCase
22+
{
23+
/**
24+
* @dataProvider providesNormalizeVersionAndConstraints
25+
*/
26+
public function testVersionConstraintLogic(bool $expectedResult, ?string $version, ?string $since, ?string $until)
27+
{
28+
$versionConstraint = new VersionConstraint(since: $since, until: $until);
29+
$this->assertSame($expectedResult, $versionConstraint->isVersionCompatible($version));
30+
}
31+
32+
public static function providesNormalizeVersionAndConstraints(): \Generator
33+
{
34+
yield 'Version in range' => [true, '1.2', '1.1', '1.5'];
35+
yield 'Version below range with both limits' => [false, '0.9', '1.1', '1.5'];
36+
yield 'Version below range only with lower limit' => [false, '0.9', '1.1', null];
37+
yield 'Version in range only with upper limit' => [true, '0.9', null, '1.5'];
38+
yield 'Version above range with both limits' => [false, '2.0', '1.1', '1.5'];
39+
yield 'Version above range only with upper limit' => [false, '2.0', null, '1.5'];
40+
yield 'Version in range only with low limit ' => [true, '2.0', '1.1', null];
41+
yield 'No version to no limits' => [false, '', '1.1', '1.5'];
42+
}
43+
44+
public function testSinceParameterNotEmptyString()
45+
{
46+
$this->expectException(InvalidArgumentException::class);
47+
$this->expectExceptionMessage('Parameter "since" of annotation "Symfony\Component\Serializer\Annotation\VersionConstraint" must be a non-empty string.');
48+
new VersionConstraint(since: '', until: '2.0');
49+
}
50+
51+
public function testUntilParameterNotEmptyString()
52+
{
53+
$this->expectException(InvalidArgumentException::class);
54+
$this->expectExceptionMessage('Parameter "until" of annotation "Symfony\Component\Serializer\Annotation\VersionConstraint" must be a non-empty string.');
55+
new VersionConstraint(since: '1.1', until: '');
56+
}
57+
58+
public function testBothSinceAndUntilParameterAreNull()
59+
{
60+
$this->expectException(InvalidArgumentException::class);
61+
$this->expectExceptionMessage('At least one of "since" or "until" properties of annotation "Symfony\Component\Serializer\Annotation\VersionConstraint" have to be defined.');
62+
new VersionConstraint(since: null, until: null);
63+
}
64+
65+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Serializer\Tests\Fixtures\Annotations;
13+
14+
use Symfony\Component\Serializer\Annotation\Version;
15+
use Symfony\Component\Serializer\Annotation\VersionConstraint;
16+
17+
/**
18+
* @author Olivier MICHAUD <[email protected]>
19+
*/
20+
class DoubleVersionDummy
21+
{
22+
private $foo;
23+
24+
/**
25+
* @Version
26+
*/
27+
public string $objectVersion1;
28+
29+
/**
30+
* @Version
31+
*/
32+
public string $objectVersion2;
33+
34+
/**
35+
* @VersionConstraint(
36+
* since="1.1",
37+
* until="1.5"
38+
* )
39+
*/
40+
public ?string $versionedProperty;
41+
}

0 commit comments

Comments
 (0)