Skip to content

Commit

Permalink
Add possibility to exclude properties from serialization based on ver…
Browse files Browse the repository at this point in the history
…sion constraints accepted by composer
  • Loading branch information
W0rma committed Oct 31, 2021
1 parent 0c83e1f commit 8e50c6b
Show file tree
Hide file tree
Showing 15 changed files with 94 additions and 5 deletions.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
},
"require-dev": {
"ext-pdo_sqlite": "*",
"composer/semver": "^1.7.4 || ^2 || ^3.2",
"doctrine/coding-standard": "^8.1",
"doctrine/orm": "~2.1",
"doctrine/persistence": "^1.3.3|^2.0|^3.0",
Expand Down
7 changes: 4 additions & 3 deletions doc/cookbook/exclusion_strategies.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,20 +57,21 @@ 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;
}
.. note ::
``@Until``, and ``@Since`` both accept a standardized PHP version number.
``@VersionConstraints`` accepts `composer version constraints <https://getcomposer.org/doc/articles/versions.md#writing-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::
Expand Down
12 changes: 12 additions & 0 deletions doc/reference/annotations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
<https://getcomposer.org/doc/articles/versions.md#writing-version-constraints>`_.

.. note ::
``composer/semver`` must be installed in your project.
@Groups
~~~~~~~
This annotation can be defined on a property to specify if the property
Expand Down
1 change: 1 addition & 0 deletions doc/reference/xml_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions doc/reference/yml_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 24 additions & 0 deletions src/Annotation/VersionConstraints.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace JMS\Serializer\Annotation;

use Composer\Semver\Semver as ComposerSemver;

/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*/
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)]
final class VersionConstraints extends Version
{
public function __construct($values = [], ?string $version = null)
{
if (!class_exists(ComposerSemver::class)) {
throw new \LogicException(sprintf('composer/semver must be installed to use "%s".', self::class));
}

parent::__construct($values, $version);
}
}
5 changes: 5 additions & 0 deletions src/Exclusion/VersionExclusionStrategy.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace JMS\Serializer\Exclusion;

use Composer\Semver\Semver;
use JMS\Serializer\Context;
use JMS\Serializer\Metadata\ClassMetadata;
use JMS\Serializer\Metadata\PropertyMetadata;
Expand All @@ -27,6 +28,10 @@ public function shouldSkipClass(ClassMetadata $metadata, Context $navigatorConte

public function shouldSkipProperty(PropertyMetadata $property, Context $navigatorContext): bool
{
if ((null !== $constraints = $property->versionConstraints) && !Semver::satisfies($this->version, $constraints)) {
return true;
}

if ((null !== $version = $property->sinceVersion) && version_compare($this->version, $version, '<')) {
return true;
}
Expand Down
3 changes: 3 additions & 0 deletions src/Metadata/Driver/AnnotationDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions src/Metadata/Driver/XmlDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
4 changes: 4 additions & 0 deletions src/Metadata/Driver/YamlDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -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']);
}
Expand Down
9 changes: 9 additions & 0 deletions src/Metadata/PropertyMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ class PropertyMetadata extends BasePropertyMetadata
* @var string
*/
public $untilVersion;
/**
* @var string
*/
public $versionConstraints;
/**
* @var string[]
*/
Expand Down Expand Up @@ -239,6 +243,7 @@ public function serialize()
'excludeIf' => $this->excludeIf,
'skipWhenEmpty' => $this->skipWhenEmpty,
'forceReflectionAccess' => $this->forceReflectionAccess,
'versionConstraints' => $this->versionConstraints,
]);
}

Expand Down Expand Up @@ -304,6 +309,10 @@ protected function unserializeProperties(string $str): string
$this->forceReflectionAccess = $unserialized['forceReflectionAccess'];
}

if (isset($unserialized['versionConstraints'])) {
$this->versionConstraints = $unserialized['versionConstraints'];
}

return $parentStr;
}
}
22 changes: 22 additions & 0 deletions tests/Fixtures/ObjectWithVersionedVirtualProperties.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -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
{
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion tests/Serializer/BaseSerializationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion tests/Serializer/JsonSerializationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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"}';
Expand Down
2 changes: 2 additions & 0 deletions tests/Serializer/xml/virtual_properties_all.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<result>
<classlow>1</classlow>
<classsemver>61</classsemver>
<classhigh>8</classhigh>
<low>1</low>
<semver>61</semver>
<high>8</high>
</result>

0 comments on commit 8e50c6b

Please sign in to comment.