From c2a0e190c7be7cbcd22186722aa71754271af621 Mon Sep 17 00:00:00 2001 From: W0rma Date: Sun, 31 Oct 2021 13:29:53 +0100 Subject: [PATCH] Add possibility to exclude properties from serialization based on version constraints accepted by composer --- composer.json | 2 ++ doc/cookbook/exclusion_strategies.rst | 7 +++--- doc/reference/annotations.rst | 12 ++++++++++ doc/reference/xml_reference.rst | 1 + doc/reference/yml_reference.rst | 1 + src/Annotation/VersionConstraints.php | 24 +++++++++++++++++++ src/Exclusion/VersionExclusionStrategy.php | 5 ++++ src/Metadata/Driver/AnnotationDriver.php | 3 +++ src/Metadata/Driver/XmlDriver.php | 4 ++++ src/Metadata/Driver/YamlDriver.php | 4 ++++ src/Metadata/PropertyMetadata.php | 9 +++++++ .../ObjectWithVersionedVirtualProperties.php | 22 +++++++++++++++++ tests/Serializer/BaseSerializationTest.php | 2 +- tests/Serializer/JsonSerializationTest.php | 2 +- .../Serializer/xml/virtual_properties_all.xml | 2 ++ 15 files changed, 95 insertions(+), 5 deletions(-) create mode 100644 src/Annotation/VersionConstraints.php diff --git a/composer.json b/composer.json index c0ea8f156..123cb5770 100644 --- a/composer.json +++ b/composer.json @@ -24,12 +24,14 @@ "phpstan/phpdoc-parser": "^0.4 || ^0.5 || ^1.0" }, "suggest": { + "composer/semver": "Required if you like to exclude properties from serialization based on version constraints.", "doctrine/collections": "Required if you like to use doctrine collection types as ArrayCollection.", "symfony/cache": "Required if you like to use cache functionality.", "symfony/yaml": "Required if you'd like to use the YAML metadata format." }, "require-dev": { "ext-pdo_sqlite": "*", + "composer/semver": "^1.7.4 || ^2.0 || ^3.0", "doctrine/coding-standard": "^8.1", "doctrine/orm": "~2.1", "doctrine/persistence": "^1.3.3|^2.0|^3.0", diff --git a/doc/cookbook/exclusion_strategies.rst b/doc/cookbook/exclusion_strategies.rst index 90dab33dd..f6a6803f9 100644 --- a/doc/cookbook/exclusion_strategies.rst +++ b/doc/cookbook/exclusion_strategies.rst @@ -57,12 +57,12 @@ expose them via an API that is consumed by a third-party: class VersionedObject { /** - * @Until("1.0.x") + * @VersionConstraints("<1.1") */ private $name; /** - * @Since("1.1") + * @VersionConstraints(">=1.1") * @SerializedName("name") */ private $name2; @@ -70,7 +70,8 @@ expose them via an API that is consumed by a third-party: .. note :: - ``@Until``, and ``@Since`` both accept a standardized PHP version number. + ``@VersionConstraints`` accepts `composer version constraints `_. + ``composer/semver`` must be installed in your project. If you have annotated your objects like above, you can serializing different versions like this:: diff --git a/doc/reference/annotations.rst b/doc/reference/annotations.rst index 0808b1366..3e965fecf 100644 --- a/doc/reference/annotations.rst +++ b/doc/reference/annotations.rst @@ -76,6 +76,18 @@ property was available. If a later version is serialized, then this property is excluded automatically. The version must be in a format that is understood by PHP's ``version_compare`` function. +@VersionConstraints +~~~~~~ +This annotation can be defined on a property to specify the version constraints +for which this property is available. This property is excluded automatically if +a version is serialized which does not satisfy the constraints. The constraints +must be in a format that is understood by `composer +`_. + +.. note :: + + ``composer/semver`` must be installed in your project. + @Groups ~~~~~~~ This annotation can be defined on a property to specify if the property diff --git a/doc/reference/xml_reference.rst b/doc/reference/xml_reference.rst index 239b8310b..eff3b21a3 100644 --- a/doc/reference/xml_reference.rst +++ b/doc/reference/xml_reference.rst @@ -79,6 +79,7 @@ XML Reference serialized-name="foo" since-version="1.0" until-version="1.1" + version-constraints=">=1.0 <1.2" xml-attribute="true" access-type="public_method" accessor-getter="getSomeProperty" diff --git a/doc/reference/yml_reference.rst b/doc/reference/yml_reference.rst index b7ba44ddc..1668f5d9d 100644 --- a/doc/reference/yml_reference.rst +++ b/doc/reference/yml_reference.rst @@ -51,6 +51,7 @@ YAML Reference serialized_name: foo since_version: 1.0 until_version: 1.1 + version_constraints: ">=1.0 <1.2" groups: [foo, bar] xml_attribute: true xml_value: true diff --git a/src/Annotation/VersionConstraints.php b/src/Annotation/VersionConstraints.php new file mode 100644 index 000000000..29fb7ca39 --- /dev/null +++ b/src/Annotation/VersionConstraints.php @@ -0,0 +1,24 @@ +versionConstraints) && !Semver::satisfies($this->version, $constraints)) { + return true; + } + if ((null !== $version = $property->sinceVersion) && version_compare($this->version, $version, '<')) { return true; } diff --git a/src/Metadata/Driver/AnnotationDriver.php b/src/Metadata/Driver/AnnotationDriver.php index 8cda61bc3..eaa9b6bf0 100644 --- a/src/Metadata/Driver/AnnotationDriver.php +++ b/src/Metadata/Driver/AnnotationDriver.php @@ -24,6 +24,7 @@ use JMS\Serializer\Annotation\SkipWhenEmpty; use JMS\Serializer\Annotation\Type; use JMS\Serializer\Annotation\Until; +use JMS\Serializer\Annotation\VersionConstraints; use JMS\Serializer\Annotation\VirtualProperty; use JMS\Serializer\Annotation\XmlAttribute; use JMS\Serializer\Annotation\XmlAttributeMap; @@ -182,6 +183,8 @@ public function loadMetadataForClass(\ReflectionClass $class): ?BaseClassMetadat $propertyMetadata->sinceVersion = $annot->version; } elseif ($annot instanceof Until) { $propertyMetadata->untilVersion = $annot->version; + } elseif ($annot instanceof VersionConstraints) { + $propertyMetadata->versionConstraints = $annot->version; } elseif ($annot instanceof SerializedName) { $propertyMetadata->serializedName = $annot->name; } elseif ($annot instanceof SkipWhenEmpty) { diff --git a/src/Metadata/Driver/XmlDriver.php b/src/Metadata/Driver/XmlDriver.php index e0238f504..47b2259eb 100644 --- a/src/Metadata/Driver/XmlDriver.php +++ b/src/Metadata/Driver/XmlDriver.php @@ -219,6 +219,10 @@ protected function loadMetadataFromFile(\ReflectionClass $class, string $path): $pMetadata->untilVersion = (string) $version; } + if (null !== $constraints = $pElem->attributes()->{'version-constraints'}) { + $pMetadata->versionConstraints = (string) $constraints; + } + if (null !== $serializedName = $pElem->attributes()->{'serialized-name'}) { $pMetadata->serializedName = (string) $serializedName; } diff --git a/src/Metadata/Driver/YamlDriver.php b/src/Metadata/Driver/YamlDriver.php index e7880c6a1..d5cbf7d7b 100644 --- a/src/Metadata/Driver/YamlDriver.php +++ b/src/Metadata/Driver/YamlDriver.php @@ -189,6 +189,10 @@ protected function loadMetadataFromFile(ReflectionClass $class, string $file): ? $pMetadata->untilVersion = (string) $pConfig['until_version']; } + if (isset($pConfig['version_constraints'])) { + $pMetadata->versionConstraints = (string) $pConfig['version_constraints']; + } + if (isset($pConfig['exclude_if'])) { $pMetadata->excludeIf = $this->parseExpression((string) $pConfig['exclude_if']); } diff --git a/src/Metadata/PropertyMetadata.php b/src/Metadata/PropertyMetadata.php index f7d0f513c..6027c9a91 100644 --- a/src/Metadata/PropertyMetadata.php +++ b/src/Metadata/PropertyMetadata.php @@ -21,6 +21,10 @@ class PropertyMetadata extends BasePropertyMetadata * @var string */ public $untilVersion; + /** + * @var string + */ + public $versionConstraints; /** * @var string[] */ @@ -239,6 +243,7 @@ public function serialize() 'excludeIf' => $this->excludeIf, 'skipWhenEmpty' => $this->skipWhenEmpty, 'forceReflectionAccess' => $this->forceReflectionAccess, + 'versionConstraints' => $this->versionConstraints, ]); } @@ -304,6 +309,10 @@ protected function unserializeProperties(string $str): string $this->forceReflectionAccess = $unserialized['forceReflectionAccess']; } + if (isset($unserialized['versionConstraints'])) { + $this->versionConstraints = $unserialized['versionConstraints']; + } + return $parentStr; } } diff --git a/tests/Fixtures/ObjectWithVersionedVirtualProperties.php b/tests/Fixtures/ObjectWithVersionedVirtualProperties.php index 30acf5dfc..2380bc2d0 100644 --- a/tests/Fixtures/ObjectWithVersionedVirtualProperties.php +++ b/tests/Fixtures/ObjectWithVersionedVirtualProperties.php @@ -8,6 +8,7 @@ use JMS\Serializer\Annotation\SerializedName; use JMS\Serializer\Annotation\Since; use JMS\Serializer\Annotation\Until; +use JMS\Serializer\Annotation\VersionConstraints; use JMS\Serializer\Annotation\VirtualProperty; /** @@ -19,12 +20,18 @@ * options={@Until("8")} * ) * @VirtualProperty( + * "classsemver", + * exp="object.getVirtualValue(61)", + * options={@VersionConstraints("^6.1")} + * ) + * @VirtualProperty( * "classhigh", * exp="object.getVirtualValue(8)", * options={@Since("6")} * ) */ #[VirtualProperty(name: 'classlow', exp: 'object.getVirtualValue(1)', options: [[Until::class, ['8']]])] +#[VirtualProperty(name: 'classsemver', exp: 'object.getVirtualValue(61)', options: [[VersionConstraints::class, ['^6.1']]])] #[VirtualProperty(name: 'classhigh', exp: 'object.getVirtualValue(8)', options: [[Since::class, ['6']]])] class ObjectWithVersionedVirtualProperties { @@ -43,6 +50,21 @@ public function getVirtualLowValue() return 1; } + /** + * @Groups({"versions"}) + * @VirtualProperty + * @SerializedName("semver") + * @VersionConstraints("6.1") + */ + #[Groups(groups: ['versions'])] + #[VirtualProperty] + #[SerializedName(name: 'semver')] + #[VersionConstraints('^6.1')] + public function getVirtualSemverValue() + { + return 61; + } + /** * @Groups({"versions"}) * @VirtualProperty diff --git a/tests/Serializer/BaseSerializationTest.php b/tests/Serializer/BaseSerializationTest.php index ec18bac09..1b7bc659e 100644 --- a/tests/Serializer/BaseSerializationTest.php +++ b/tests/Serializer/BaseSerializationTest.php @@ -1333,7 +1333,7 @@ public function testVirtualVersions() self::assertEquals( $this->getContent('virtual_properties_all'), - $serializer->serialize(new ObjectWithVersionedVirtualProperties(), $this->getFormat(), SerializationContext::create()->setVersion('7')) + $serializer->serialize(new ObjectWithVersionedVirtualProperties(), $this->getFormat(), SerializationContext::create()->setVersion('6.1')) ); self::assertEquals( diff --git a/tests/Serializer/JsonSerializationTest.php b/tests/Serializer/JsonSerializationTest.php index e76c66599..84d1886e8 100644 --- a/tests/Serializer/JsonSerializationTest.php +++ b/tests/Serializer/JsonSerializationTest.php @@ -82,7 +82,7 @@ protected function getContent($key) $outputs['virtual_properties'] = '{"exist_field":"value","virtual_value":"value","test":"other-name","typed_virtual_property":1}'; $outputs['virtual_properties_low'] = '{"classlow":1,"low":1}'; $outputs['virtual_properties_high'] = '{"classhigh":8,"high":8}'; - $outputs['virtual_properties_all'] = '{"classlow":1,"classhigh":8,"low":1,"high":8}'; + $outputs['virtual_properties_all'] = '{"classlow":1,"classsemver":61,"classhigh":8,"low":1,"semver":61,"high":8}'; $outputs['nullable'] = '{"foo":"bar","baz":null,"0":null}'; $outputs['nullable_skip'] = '{"foo":"bar"}'; $outputs['person_secret_show'] = '{"name":"mike","gender":"f"}'; diff --git a/tests/Serializer/xml/virtual_properties_all.xml b/tests/Serializer/xml/virtual_properties_all.xml index 80fb1917f..2cf8ea4fd 100644 --- a/tests/Serializer/xml/virtual_properties_all.xml +++ b/tests/Serializer/xml/virtual_properties_all.xml @@ -1,7 +1,9 @@ 1 + 61 8 1 + 61 8