Skip to content

Commit

Permalink
Add possibility to exclude properties from serialization based on sem…
Browse files Browse the repository at this point in the history
…ver constraints
  • Loading branch information
W0rma committed Oct 31, 2021
1 parent 0c83e1f commit 62d92f6
Show file tree
Hide file tree
Showing 15 changed files with 96 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")
* @Semver("~1.0.0")
*/
private $name;
/**
* @Since("1.1")
* @Semver(">=1.1")
* @SerializedName("name")
*/
private $name2;
}
.. note ::
``@Until``, and ``@Since`` both accept a standardized PHP version number.
``@Semver`` 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.

@Semver
~~~~~~
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"
semver-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
semver_constraints: ">=1.0 <1.2"
groups: [foo, bar]
xml_attribute: true
xml_value: true
Expand Down
24 changes: 24 additions & 0 deletions src/Annotation/Semver.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 Semver 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);
}
}
7 changes: 7 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,12 @@ public function shouldSkipClass(ClassMetadata $metadata, Context $navigatorConte

public function shouldSkipProperty(PropertyMetadata $property, Context $navigatorContext): bool
{
//if ((null !== $constraints = $property->semverConstraints) && Semver::satisfies($this->version, $constraints)) {
if ((null !== $constraints = $property->semverConstraints) && !Semver::satisfies($this->version, $constraints)) {
//var_dump('version: '.$this->version.', constraints: '.$constraints.', satisfies: '.var_export(Semver::satisfies($this->version, $constraints), true));
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 @@ -19,6 +19,7 @@
use JMS\Serializer\Annotation\PostSerialize;
use JMS\Serializer\Annotation\PreSerialize;
use JMS\Serializer\Annotation\ReadOnlyProperty;
use JMS\Serializer\Annotation\Semver;
use JMS\Serializer\Annotation\SerializedName;
use JMS\Serializer\Annotation\Since;
use JMS\Serializer\Annotation\SkipWhenEmpty;
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 Semver) {
$propertyMetadata->semverConstraints = $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()->{'semver-constraints'}) {
$pMetadata->semverConstraints = (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['semver_constraints'])) {
$pMetadata->semverConstraints = (string) $pConfig['semver_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 $semverConstraints;
/**
* @var string[]
*/
Expand Down Expand Up @@ -239,6 +243,7 @@ public function serialize()
'excludeIf' => $this->excludeIf,
'skipWhenEmpty' => $this->skipWhenEmpty,
'forceReflectionAccess' => $this->forceReflectionAccess,
'semverConstraints' => $this->semverConstraints,
]);
}

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

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

return $parentStr;
}
}
22 changes: 22 additions & 0 deletions tests/Fixtures/ObjectWithVersionedVirtualProperties.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace JMS\Serializer\Tests\Fixtures;

use JMS\Serializer\Annotation\Groups;
use JMS\Serializer\Annotation\Semver;
use JMS\Serializer\Annotation\SerializedName;
use JMS\Serializer\Annotation\Since;
use JMS\Serializer\Annotation\Until;
Expand All @@ -19,12 +20,18 @@
* options={@Until("8")}
* )
* @VirtualProperty(
* "classsemver",
* exp="object.getVirtualValue(61)",
* options={@Semver("^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: [[Semver::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")
* @Semver("6.1")
*/
#[Groups(groups: ['versions'])]
#[VirtualProperty]
#[SerializedName(name: 'semver')]
#[Semver('^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 62d92f6

Please sign in to comment.