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

Skip to content

[Serializer] Add versionning #50850

New issue

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

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

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: 7.3
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions src/Symfony/Component/Serializer/Annotation/Version.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

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

namespace Symfony\Component\Serializer\Annotation;

/**
* Annotation class for @Version().
*
* @Annotation
*
* @NamedArgumentConstructor
*
* @Target({"PROPERTY", "METHOD"})
*
* @author Olivier Michaud <[email protected]>
*/
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)]
final class Version
{
public function __construct()
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

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

namespace Symfony\Component\Serializer\Annotation;

use Symfony\Component\Serializer\Exception\InvalidArgumentException;

/**
* Annotation class for @VersionConstraint().
*
* @Annotation
*
* @NamedArgumentConstructor
*
* @Target({"PROPERTY", "METHOD"})
*
* @author Olivier Michaud <[email protected]>
*/
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)]
final class VersionConstraint
{
public function __construct(private readonly ?string $since = null, private readonly ?string $until = null)
{
if ('' === $since) {
throw new InvalidArgumentException(sprintf('Parameter "since" of annotation "%s" must be a non-empty string.', self::class));
}
if ('' === $until) {
throw new InvalidArgumentException(sprintf('Parameter "until" of annotation "%s" must be a non-empty string.', self::class));
}
if (null === $since && null === $until) {
throw new InvalidArgumentException(sprintf('At least one of "since" or "until" properties of annotation "%s" have to be defined.', self::class));
}
}

public function isVersionCompatible(string $version): bool
{
if ($this->since) {
if (!version_compare($version, $this->since, '>=')) {
return false;
}
}
if ($this->until) {
if (!version_compare($version, $this->until, '<=')) {
return false;
}
}

return true;
}
}
5 changes: 5 additions & 0 deletions src/Symfony/Component/Serializer/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
CHANGELOG
=========

6.4
---

* Add `Version` and `VersionConstraint` annotation/attributes to help versioning on normalize/denormalize operations

6.3
---

Expand Down
49 changes: 48 additions & 1 deletion src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
namespace Symfony\Component\Serializer\Mapping;

use Symfony\Component\PropertyAccess\PropertyPath;
use Symfony\Component\Serializer\Annotation\VersionConstraint;

/**
* @author Kévin Dunglas <[email protected]>
Expand Down Expand Up @@ -78,6 +79,27 @@ class AttributeMetadata implements AttributeMetadataInterface
*/
public array $denormalizationContexts = [];

/**
* @internal This property is public in order to reduce the size of the
* class' serialized representation. Do not access it. Use
* {@link isVersion()} instead.
*/
public bool $version = false;

/**
* @internal This property is public in order to reduce the size of the
* class' serialized representation. Do not access it. Use
* {@link getVersionConstraint()} instead.
*/
public ?VersionConstraint $versionConstraint = null;

/**
* @internal This property is public in order to reduce the size of the
* class' serialized representation. Do not access it. Use
* {@link isVersionCompatible()} instead.
*/
public ?\Closure $versionConstraintCallable = null;

public function __construct(string $name)
{
$this->name = $name;
Expand Down Expand Up @@ -144,6 +166,31 @@ public function isIgnored(): bool
return $this->ignore;
}

public function setVersion(bool $version): void
{
$this->version = $version;
}

public function isVersion(): bool
{
return $this->version;
}

public function setVersionConstraint(VersionConstraint $versionConstraint): void
{
$this->versionConstraint = $versionConstraint;
}

public function getVersionConstraint(): ?VersionConstraint
{
return $this->versionConstraint;
}

public function isVersionCompatible(string $version): bool
{
return !$this->versionConstraint || $this->versionConstraint->isVersionCompatible($version);
}

public function getNormalizationContexts(): array
{
return $this->normalizationContexts;
Expand Down Expand Up @@ -225,6 +272,6 @@ public function merge(AttributeMetadataInterface $attributeMetadata): void
*/
public function __sleep(): array
{
return ['name', 'groups', 'maxDepth', 'serializedName', 'serializedPath', 'ignore', 'normalizationContexts', 'denormalizationContexts'];
return ['name', 'groups', 'maxDepth', 'serializedName', 'serializedPath', 'ignore', 'normalizationContexts', 'denormalizationContexts', 'version', 'versionConstraint'];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
namespace Symfony\Component\Serializer\Mapping;

use Symfony\Component\PropertyAccess\PropertyPath;
use Symfony\Component\Serializer\Annotation\VersionConstraint;

/**
* Stores metadata needed for serializing and deserializing attributes.
Expand Down Expand Up @@ -75,6 +76,20 @@ public function setIgnore(bool $ignore): void;
*/
public function isIgnored(): bool;

/**
* Sets if this attribute is holding the version. Only one attribute can hold version at once.
*/
public function setVersion(bool $version): void;

/**
* Gets if this attribute is holding the version.
*/
public function isVersion(): bool;

public function setVersionConstraint(VersionConstraint $versionConstraint): void;

public function isVersionCompatible(string $version): bool;

/**
* Merges an {@see AttributeMetadataInterface} with in the current one.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
use Symfony\Component\Serializer\Annotation\MaxDepth;
use Symfony\Component\Serializer\Annotation\SerializedName;
use Symfony\Component\Serializer\Annotation\SerializedPath;
use Symfony\Component\Serializer\Annotation\Version;
use Symfony\Component\Serializer\Annotation\VersionConstraint;
use Symfony\Component\Serializer\Exception\LogicException;
use Symfony\Component\Serializer\Exception\MappingException;
use Symfony\Component\Serializer\Mapping\AttributeMetadata;
use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface;
Expand All @@ -37,6 +40,8 @@ class AnnotationLoader implements LoaderInterface
DiscriminatorMap::class,
Groups::class,
Ignore::class,
Version::class,
VersionConstraint::class,
MaxDepth::class,
SerializedName::class,
SerializedPath::class,
Expand Down Expand Up @@ -65,6 +70,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool
}
}

$hasVersionProperty = false;
foreach ($reflectionClass->getProperties() as $property) {
if (!isset($attributesMetadata[$property->name])) {
$attributesMetadata[$property->name] = new AttributeMetadata($property->name);
Expand All @@ -85,6 +91,17 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool
$attributesMetadata[$property->name]->setSerializedPath($annotation->getSerializedPath());
} elseif ($annotation instanceof Ignore) {
$attributesMetadata[$property->name]->setIgnore(true);
} elseif ($annotation instanceof VersionConstraint) {
if (!($property->getType() === null || $property->getType()->allowsNull())) {
throw new LogicException(sprintf('VersionConstraint on "%s::%s()" cannot be added. Property should either have no typehint either be declared as nullable.', $className, $property->name));
}
$attributesMetadata[$property->name]->setVersionConstraint($annotation);
} elseif ($annotation instanceof Version) {
if ($hasVersionProperty) {
throw new LogicException(sprintf('Version on "%s::%s()" cannot be added. Version holder property can only be set once.', $className, $property->name));
}
$attributesMetadata[$property->name]->setVersion(true);
$hasVersionProperty = true;
} elseif ($annotation instanceof Context) {
$this->setAttributeContextsForGroups($annotation, $attributesMetadata[$property->name]);
}
Expand Down Expand Up @@ -114,7 +131,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool
$classMetadata->addAttributeMetadata($attributeMetadata);
}
}

$hasVersionProperty = false;
foreach ($this->loadAnnotations($method) as $annotation) {
if ($annotation instanceof Groups) {
if (!$accessorOrMutator) {
Expand Down Expand Up @@ -148,6 +165,23 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool
}

$attributeMetadata->setIgnore(true);
} elseif ($annotation instanceof VersionConstraint) {
if (!$accessorOrMutator) {
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));
}

$attributeMetadata->setVersionConstraint($annotation);
} elseif ($annotation instanceof Version) {
if (!$accessorOrMutator) {
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));
}

if ($hasVersionProperty) {
throw new LogicException(sprintf('Version on "%s::%s()" cannot be added. Version holder property can only be set once.', $className, $method->name));
}

$attributeMetadata->setVersion(true);
$hasVersionProperty = true;
} elseif ($annotation instanceof Context) {
if (!$accessorOrMutator) {
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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -552,4 +552,16 @@ protected function getAttributeMetadata(object|string $objectOrClass, string $at

return $this->classMetadataFactory->getMetadataFor($objectOrClass)->getAttributesMetadata()[$attribute] ?? null;
}

/**
* @param array<string, AttributeMetadataInterface> $attributesMetadata
*/
protected function isVersionCompatible(string $version, array $attributesMetadata, string $attributeName): bool
{
if (!isset($attributesMetadata[$attributeName])) {
return true;
}

return $attributesMetadata[$attributeName]->isVersionCompatible($version);
}
}
Loading