From 546c3924d3f6caf285ca5b3514a0f73119d5c2b4 Mon Sep 17 00:00:00 2001 From: Asmir Mustafic Date: Wed, 10 Apr 2019 15:45:44 +0200 Subject: [PATCH 1/4] revert v2 nested groups --- src/Exclusion/GroupsExclusionStrategy.php | 19 +++++-------------- .../Exclusion/GroupsExclusionStrategyTest.php | 6 +++--- tests/Serializer/JsonSerializationTest.php | 2 +- tests/Serializer/xml/groups_advanced.xml | 4 ++-- 4 files changed, 11 insertions(+), 20 deletions(-) diff --git a/src/Exclusion/GroupsExclusionStrategy.php b/src/Exclusion/GroupsExclusionStrategy.php index 6a3d0d742..66762826f 100644 --- a/src/Exclusion/GroupsExclusionStrategy.php +++ b/src/Exclusion/GroupsExclusionStrategy.php @@ -5,7 +5,6 @@ namespace JMS\Serializer\Exclusion; use JMS\Serializer\Context; -use JMS\Serializer\Exception\RuntimeException; use JMS\Serializer\Metadata\ClassMetadata; use JMS\Serializer\Metadata\PropertyMetadata; @@ -99,26 +98,18 @@ public function getGroupsFor(Context $navigatorContext): array $paths = $navigatorContext->getCurrentPath(); $groups = $this->groups; - $single = array_filter($groups, 'is_string'); foreach ($paths as $index => $path) { if (!array_key_exists($path, $groups)) { + if ($index > 0) { + $groups = [self::DEFAULT_GROUP]; + } break; } - - if (!is_array($groups[$path])) { - throw new RuntimeException(sprintf('The group value for the property path "%s" should be an array, "%s" given', $index, gettype($groups[$path]))); - } - $groups = $groups[$path]; - - $newSingle = array_filter($groups, 'is_string'); - if (!count($newSingle)) { - $groups += $single; - } else { - $single = $newSingle; + if (!array_filter($groups, 'is_string')) { + $groups += [self::DEFAULT_GROUP]; } } - return $groups; } } diff --git a/tests/Exclusion/GroupsExclusionStrategyTest.php b/tests/Exclusion/GroupsExclusionStrategyTest.php index 33417e6b1..e687586d5 100644 --- a/tests/Exclusion/GroupsExclusionStrategyTest.php +++ b/tests/Exclusion/GroupsExclusionStrategyTest.php @@ -80,12 +80,12 @@ public function getGroupsFor() [['foo', 'prop' => ['bar']], ['prop'], ['bar']], [['foo', 'prop' => ['bar']], ['prop2'], ['foo', 'prop' => ['bar']]], - [['foo', 'prop' => ['bar']], ['prop', 'prop2'], ['bar']], + [['foo', 'prop' => ['bar']], ['prop', 'prop2'], ['Default']], - [['foo', 'prop' => ['xx', 'prop2' => ['def'], 'prop3' => ['def']]], ['prop', 'prop2', 'propB'], ['def']], + [['foo', 'prop' => ['xx', 'prop2' => ['def'], 'prop3' => ['def']]], ['prop', 'prop2', 'propB'], ['Default']], [['foo', 'prop' => ['xx', 'prop2' => ['def', 'prop3' => ['def']]]], ['prop', 'prop2'], ['def', 'prop3' => ['def']]], - [['foo', 'prop' => ['prop2' => ['prop3' => ['def']]]], ['prop', 'prop2'], ['foo', 'prop3' => ['def']]], + [['foo', 'prop' => ['prop2' => ['prop3' => ['def']]]], ['prop', 'prop2'], ['Default', 'prop3' => ['def']]], ]; } } diff --git a/tests/Serializer/JsonSerializationTest.php b/tests/Serializer/JsonSerializationTest.php index 9d393b5df..15c335e2c 100644 --- a/tests/Serializer/JsonSerializationTest.php +++ b/tests/Serializer/JsonSerializationTest.php @@ -76,7 +76,7 @@ protected function getContent($key) $outputs['groups_foo'] = '{"foo":"foo","foobar":"foobar"}'; $outputs['groups_foobar'] = '{"foo":"foo","foobar":"foobar","bar":"bar"}'; $outputs['groups_default'] = '{"bar":"bar","none":"none"}'; - $outputs['groups_advanced'] = '{"name":"John","manager":{"name":"John Manager","friends":[{"nickname":"nickname"},{"nickname":"nickname"}]},"friends":[{"nickname":"nickname","manager":{"nickname":"nickname"}},{"nickname":"nickname","manager":{"nickname":"nickname"}}]}'; + $outputs['groups_advanced'] = '{"name":"John","manager":{"name":"John Manager","friends":[{"nickname":"nickname"},{"nickname":"nickname"}]},"friends":[{"nickname":"nickname","manager":{"name":"John friend 1 manager"}},{"nickname":"nickname","manager":{"name":"John friend 2 manager"}}]}'; $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}'; diff --git a/tests/Serializer/xml/groups_advanced.xml b/tests/Serializer/xml/groups_advanced.xml index 8755aca84..0970633e4 100644 --- a/tests/Serializer/xml/groups_advanced.xml +++ b/tests/Serializer/xml/groups_advanced.xml @@ -16,13 +16,13 @@ - + - + From c217817b915d23c71ae718230c1714065662471f Mon Sep 17 00:00:00 2001 From: Asmir Mustafic Date: Wed, 10 Apr 2019 15:48:06 +0200 Subject: [PATCH 2/4] increase branch alias --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 2d14dccb1..ba283b38e 100644 --- a/composer.json +++ b/composer.json @@ -61,7 +61,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.3-dev" + "dev-master": "3.0-dev" } } } From 6a5895ac73afbc060e1aedd771d53789abde16ba Mon Sep 17 00:00:00 2001 From: Asmir Mustafic Date: Wed, 10 Apr 2019 15:49:22 +0200 Subject: [PATCH 3/4] update docs --- README.md | 29 +- UPGRADING.md | 13 + doc/cookbook/exclusion_strategies.rst | 663 +++++++++++++------------- 3 files changed, 357 insertions(+), 348 deletions(-) diff --git a/README.md b/README.md index c25036bef..d63f1cc76 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,8 @@ # jms/serializer -| [Master][Master] | [1.x][1.x] | -|:----------------:|:----------:| -| [![Build status][Master image]][Master] | [![Build status][1.x image]][1.x] | -| [![Coverage Status][Master coverage image]][Master coverage] | [![Coverage Status][1.x coverage image]][1.x coverage] | + +[![Build status][Master image]][Master] +[![Coverage Status][Master coverage image]][Master coverage] ![alt text](doc/logo-small.png) @@ -28,17 +27,16 @@ Learn more about the serializer in its [documentation](http://jmsyst.com/libs/se ## Notes -You are browsing the code for the 2.x version, if you are interested in the 1.x version, -check the [1.x][1.x] branch. +You are browsing the code for the 3.x version, if you are interested in the 1.x or 2.x version, +check the [1.x][1.x] and [2.x][2.x] branches. -Differences between the 1.x and 2.x can be found in the [CHANGELOG][CHANGELOG]. -Upgrading from 1.x to 2.x should be almost transparent for most of the userland code, in the -case you have heavily used internal-api or you are relaying on some of the removed features, -the [UPGRADING][UPGRADING] document is a short guide on how to upgrade. +Instructions on how to upgrade available in the [UPGRADING][UPGRADING] document. -Pull requests for new features are accepted only on the master branch. -Bug fixes are accepted for both master and 1.x branches. -Bug fixes sent on the 1.x branch, will be ported to the master branch when possible. +- `3.x` is the active and supported version (`master` branch). +- `2.x` is not supported anymore (`2.x` branch). +- `1.x` is in maintenance mode, there will be no active development but PRs are accepted (`1.x` branch). + +Bug fixes sent on the 1.x branch, will be ported to the master branch when possible. [CHANGELOG]: https://github.com/schmittjoh/serializer/blob/master/CHANGELOG.md [UPGRADING]: https://github.com/schmittjoh/serializer/blob/master/UPGRADING.md @@ -48,8 +46,5 @@ Bug fixes sent on the 1.x branch, will be ported to the master branch when possi [Master coverage image]: https://img.shields.io/scrutinizer/coverage/g/schmittjoh/serializer/master.svg?style=flat-square [Master coverage]: https://scrutinizer-ci.com/g/schmittjoh/serializer/?branch=master - [1.x image]: https://img.shields.io/travis/schmittjoh/serializer/1.x.svg?style=flat-square [1.x]: https://github.com/schmittjoh/serializer/tree/1.x - [1.x coverage image]: https://img.shields.io/scrutinizer/coverage/g/schmittjoh/serializer/1.x.svg?style=flat-square - [1.x coverage]: https://scrutinizer-ci.com/g/schmittjoh/serializer/?branch=1.x - + [2.x]: https://github.com/schmittjoh/serializer/tree/2.x diff --git a/UPGRADING.md b/UPGRADING.md index d5420c2ba..692e5c759 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,3 +1,16 @@ +From 2.x to 3.0.0 +==================== + +The "deeper branch group exclusion strategy" change introduced in 2.0.0 has been reverted, +now it behaves as it as in 1.x. + +From 1.x to 3.0.0 +==================== + +Please follow the upgrade **"From 1.12.0 to 2.0.0"**, skipping the section: + +> "deeper branch group exclusion strategy" has a different behaviour, the latest group is used instead of falling back to "Default" + From 1.12.0 to 2.0.0 ==================== diff --git a/doc/cookbook/exclusion_strategies.rst b/doc/cookbook/exclusion_strategies.rst index 95ee4f00e..066dabcf2 100644 --- a/doc/cookbook/exclusion_strategies.rst +++ b/doc/cookbook/exclusion_strategies.rst @@ -1,331 +1,332 @@ -Exclusion Strategies -==================== - -Introduction ------------- -The serializer supports different exclusion strategies. Each strategy allows -you to define which properties of your objects should be serialized. - -General Exclusion Strategies ----------------------------- -If you would like to always expose, or exclude certain properties. Then, you can -do this with the annotations ``@ExclusionPolicy``, ``@Exclude``, and ``@Expose``. - -The default exclusion policy is to exclude nothing. That is, all properties of the -object will be serialized. If you only want to expose a few of the properties, -then it is easier to change the exclusion policy, and only mark these few properties: - -.. code-block :: php - - serialize(new VersionObject(), 'json', SerializationContext::create()->setVersion(1)); - - -Creating Different Views of Your Objects ----------------------------------------- -Another default exclusion strategy is to create different views of your objects. -Let's say you would like to serialize your object in a different view depending -whether it is displayed in a list view or in a details view. - -You can achieve that by using the ``@Groups`` annotation on your properties. Any -property without an explicit ``@Groups`` annotation will be included in a -``Default`` group, which can be used when specifying groups in the serialization -context. - -.. code-block :: php - - use JMS\Serializer\Annotation\Groups; - - class BlogPost - { - /** @Groups({"list", "details"}) */ - private $id; - - /** @Groups({"list", "details"}) */ - private $title; - - /** @Groups({"list"}) */ - private $nbComments; - - /** @Groups({"details"}) */ - private $comments; - - private $createdAt; - } - -You can then tell the serializer which groups to serialize in your controller:: - - use JMS\Serializer\SerializationContext; - - $serializer->serialize(new BlogPost(), 'json', SerializationContext::create()->setGroups(array('list'))); - - //will output $id, $title and $nbComments. - - $serializer->serialize(new BlogPost(), 'json', SerializationContext::create()->setGroups(array('Default', 'list'))); - - //will output $id, $title, $nbComments and $createdAt. - -Overriding Groups of Deeper Branches of the Graph -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -In some cases you want to control more precisely what is serialized because you may have the same class at different -depths of the object graph. - -For example if you have a User that has a manager and friends:: - - use JMS\Serializer\Annotation\Groups; - - class User - { - private $name; - - /** @Groups({"manager_group"}) */ - private $manager; - - /** @Groups({"friends_group"}) */ - private $friends; - - public function __construct($name, User $manager = null, array $friends = null) - { - $this->name = $name; - $this->manager = $manager; - $this->friends = $friends; - } - } - -And the following object graph:: - - $john = new User( - 'John', - new User( - 'John Manager', - new User('The boss'), - array( - new User('John Manager friend 1'), - ) - ), - array( - new User( - 'John friend 1', - new User('John friend 1 manager') - ), - new User( - 'John friend 2', - new User('John friend 2 manager') - ), - ) - ); - -You can override groups on specific paths:: - - use JMS\Serializer\SerializationContext; - - $context = SerializationContext::create()->setGroups(array( - 'Default', // Serialize John's name - 'manager_group', // Serialize John's manager - 'friends_group', // Serialize John's friends - - 'manager' => array( // Override the groups for the manager of John - 'Default', // Serialize John manager's name - 'friends_group', // Serialize John manager's friends. If you do not override the groups for the friends, it will default to Default. - ), - - 'friends' => array( // Override the groups for the friends of John - 'manager_group', // Serialize John friends' managers. - 'manager' => array( // Override the groups for the John friends' manager - 'Default', // This would be the default if you did not override the groups of the manager property. - ), - ), - )); - $serializer->serialize($john, 'json', $context); - -This would result in the following json:: - - { - "name": "John", - "manager": { - "name": "John Manager", - "friends": [ - { - "name": "John Manager friend 1" - } - ] - }, - "friends": [ - { - "manager": { - "name": "John friend 1 manager" - }, - }, - { - "manager": { - "name": "John friend 2 manager" - }, - }, - ] - } - -Limiting serialization depth of some properties ------------------------------------------------ -You can limit the depth of what will be serialized in a property with the -``@MaxDepth`` annotation. -This exclusion strategy is a bit different from the others, because it will -affect the serialized content of others classes than the one you apply the -annotation to. - -.. code-block :: php - - use JMS\Serializer\Annotation\MaxDepth; - - class User - { - private $username; - - /** @MaxDepth(1) */ - private $friends; - - /** @MaxDepth(2) */ - private $posts; - } - - class Post - { - private $title; - - private $author; - } - -In this example, serializing a user, because the max depth of the ``$friends`` -property is 1, the user friends would be serialized, but not their friends; -and because the the max depth of the ``$posts`` property is 2, the posts would -be serialized, and their author would also be serialized. - -You need to tell the serializer to take into account MaxDepth checks:: - - use JMS\Serializer\SerializationContext; - - $serializer->serialize($data, 'json', SerializationContext::create()->enableMaxDepthChecks()); - - -Dynamic exclusion strategy --------------------------- - -If the previous exclusion strategies are not enough, is possible to use the ``ExpressionLanguageExclusionStrategy`` -that uses the `symfony expression language`_ to -allow a more sophisticated exclusion strategies using ``@Exclude(if="expression")`` and ``@Expose(if="expression")`` methods. - - -.. code-block :: php - - setExpressionEvaluator(new ExpressionEvaluator(new ExpressionLanguage())) - ->build(); - -.. _symfony expression language: https://github.com/symfony/expression-language - -By default the serializer exposes three variables (`object`, `context` and `property_metadata` for use in an expression. This enables you to create custom exclusion strategies similar to i.e. the `GroupExclusionStrategy`_. In the below example, `someMethod` would receive all three variables. - -.. code-block :: php - - serialize(new VersionObject(), 'json', SerializationContext::create()->setVersion(1)); + + +Creating Different Views of Your Objects +---------------------------------------- +Another default exclusion strategy is to create different views of your objects. +Let's say you would like to serialize your object in a different view depending +whether it is displayed in a list view or in a details view. + +You can achieve that by using the ``@Groups`` annotation on your properties. Any +property without an explicit ``@Groups`` annotation will be included in a +``Default`` group, which can be used when specifying groups in the serialization +context. + +.. code-block :: php + + use JMS\Serializer\Annotation\Groups; + + class BlogPost + { + /** @Groups({"list", "details"}) */ + private $id; + + /** @Groups({"list", "details"}) */ + private $title; + + /** @Groups({"list"}) */ + private $nbComments; + + /** @Groups({"details"}) */ + private $comments; + + private $createdAt; + } + +You can then tell the serializer which groups to serialize in your controller:: + + use JMS\Serializer\SerializationContext; + + $serializer->serialize(new BlogPost(), 'json', SerializationContext::create()->setGroups(array('list'))); + + //will output $id, $title and $nbComments. + + $serializer->serialize(new BlogPost(), 'json', SerializationContext::create()->setGroups(array('Default', 'list'))); + + //will output $id, $title, $nbComments and $createdAt. + +Overriding Groups of Deeper Branches of the Graph +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +In some cases you want to control more precisely what is serialized because you may have the same class at different +depths of the object graph. + +For example if you have a User that has a manager and friends:: + + use JMS\Serializer\Annotation\Groups; + + class User + { + private $name; + + /** @Groups({"manager_group"}) */ + private $manager; + + /** @Groups({"friends_group"}) */ + private $friends; + + public function __construct($name, User $manager = null, array $friends = null) + { + $this->name = $name; + $this->manager = $manager; + $this->friends = $friends; + } + } + +And the following object graph:: + + $john = new User( + 'John', + new User( + 'John Manager', + new User('The boss'), + array( + new User('John Manager friend 1'), + ) + ), + array( + new User( + 'John friend 1', + new User('John friend 1 manager') + ), + new User( + 'John friend 2', + new User('John friend 2 manager') + ), + ) + ); + +You can override groups on specific paths:: + + use JMS\Serializer\SerializationContext; + + $context = SerializationContext::create()->setGroups(array( + 'Default', // Serialize John's name + 'manager_group', // Serialize John's manager + 'friends_group', // Serialize John's friends + + 'manager' => array( // Override the groups for the manager of John + 'Default', // Serialize John manager's name + 'friends_group', // Serialize John manager's friends. If you do not override the groups for the friends, it will default to Default. + ), + + 'friends' => array( // Override the groups for the friends of John + 'manager_group' // Serialize John friends' managers. + + 'manager' => array( // Override the groups for the John friends' manager + 'Default', // This would be the default if you did not override the groups of the manager property. + ), + ), + )); + $serializer->serialize($john, 'json', $context); + +This would result in the following json:: + + { + "name": "John", + "manager": { + "name": "John Manager", + "friends": [ + { + "name": "John Manager friend 1" + } + ] + }, + "friends": [ + { + "manager": { + "name": "John friend 1 manager" + }, + }, + { + "manager": { + "name": "John friend 2 manager" + }, + }, + ] + } + +Limiting serialization depth of some properties +----------------------------------------------- +You can limit the depth of what will be serialized in a property with the +``@MaxDepth`` annotation. +This exclusion strategy is a bit different from the others, because it will +affect the serialized content of others classes than the one you apply the +annotation to. + +.. code-block :: php + + use JMS\Serializer\Annotation\MaxDepth; + + class User + { + private $username; + + /** @MaxDepth(1) */ + private $friends; + + /** @MaxDepth(2) */ + private $posts; + } + + class Post + { + private $title; + + private $author; + } + +In this example, serializing a user, because the max depth of the ``$friends`` +property is 1, the user friends would be serialized, but not their friends; +and because the the max depth of the ``$posts`` property is 2, the posts would +be serialized, and their author would also be serialized. + +You need to tell the serializer to take into account MaxDepth checks:: + + use JMS\Serializer\SerializationContext; + + $serializer->serialize($data, 'json', SerializationContext::create()->enableMaxDepthChecks()); + + +Dynamic exclusion strategy +-------------------------- + +If the previous exclusion strategies are not enough, is possible to use the ``ExpressionLanguageExclusionStrategy`` +that uses the `symfony expression language`_ to +allow a more sophisticated exclusion strategies using ``@Exclude(if="expression")`` and ``@Expose(if="expression")`` methods. + + +.. code-block :: php + + setExpressionEvaluator(new ExpressionEvaluator(new ExpressionLanguage())) + ->build(); + +.. _symfony expression language: https://github.com/symfony/expression-language + +By default the serializer exposes three variables (`object`, `context` and `property_metadata` for use in an expression. This enables you to create custom exclusion strategies similar to i.e. the `GroupExclusionStrategy`_. In the below example, `someMethod` would receive all three variables. + +.. code-block :: php + + Date: Wed, 17 Apr 2019 09:51:19 +0200 Subject: [PATCH 4/4] update upgrade notes --- UPGRADING.md | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 692e5c759..15996edaf 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,21 +1,26 @@ From 2.x to 3.0.0 -==================== +================= + +Upgrading from 2.x to 3.x should require almost no effort. + +The only change is the revert of "deeper branch group exclusion strategy" introduced in 2.0.0 and now reverted as it +was in 1.x. If you are not using this feature, then upgrading requires no changes at all. -The "deeper branch group exclusion strategy" change introduced in 2.0.0 has been reverted, -now it behaves as it as in 1.x. +The deprecations introduced in 2.x are still present in 3.0.0, said feature are most likley to be removed in an next major. From 1.x to 3.0.0 -==================== +================= -Please follow the upgrade **"From 1.12.0 to 2.0.0"**, skipping the section: +Please follow the upgrade **"From 1.13.0 to 2.0.0"**, skipping the section: > "deeper branch group exclusion strategy" has a different behaviour, the latest group is used instead of falling back to "Default" -From 1.12.0 to 2.0.0 +The deprecations introduced in 2.x are still present in 3.0.0, said feature are most likley to be removed in an next major. + +From 1.13.0 to 2.0.0 ==================== -Upgrading from 1.x to 2.x should be almost transparent for most of the userland code, -in case you have heavily used internal-api here are the most important the changes: +If you are on version `1.x`, is suggested to migrate directly to `3.0.0` (since `2.x` is not maintained anymore). **Main changes**