diff --git a/doc/cookbook/exclusion_strategies.rst b/doc/cookbook/exclusion_strategies.rst index 90dab33dd..75f3b9641 100644 --- a/doc/cookbook/exclusion_strategies.rst +++ b/doc/cookbook/exclusion_strategies.rst @@ -224,6 +224,51 @@ This would result in the following json:: ] } +You can, also take inherited groups:: + + 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. + ), + )); + $context->enableInheritGroups(); + $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": [ + { + "name": "John friend 1", + "manager": { + "name": "John friend 1 manager" + }, + }, + { + "name": "John friend 2", + "manager": { + "name": "John friend 2 manager" + }, + }, + ] + } + Deserialization Exclusion Strategy with Groups ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You can use ``@Groups`` to cut off unwanted properties while deserialization. @@ -345,7 +390,7 @@ This also works on class level, but is only evaluated during ``serialze`` and do ``true`` is just a generic expression, you can use any expression allowed by the Symfony Expression Language -To enable this feature you have to set the Expression Evaluator when initializing the serializer. +To enable this feature you have to set the Expression Evaluator when initializing the serializer. .. code-block :: php @@ -353,7 +398,7 @@ To enable this feature you have to set the Expression Evaluator when initializin use JMS\Serializer\Expression\ExpressionEvaluator; use JMS\Serializer\Expression\SerializerBuilder; use Symfony\Component\ExpressionLanguage\ExpressionLanguage; - + $serializer = SerializerBuilder::create() ->setExpressionEvaluator(new ExpressionEvaluator(new ExpressionLanguage())) ->build(); diff --git a/src/Context.php b/src/Context.php index 36adc2e69..d4b541c0e 100644 --- a/src/Context.php +++ b/src/Context.php @@ -72,7 +72,13 @@ public function initialize(string $format, VisitorInterface $visitor, GraphNavig $this->metadataStack = new \SplStack(); if (isset($this->attributes['groups'])) { - $this->addExclusionStrategy(new GroupsExclusionStrategy($this->attributes['groups'])); + $strategy = new GroupsExclusionStrategy($this->attributes['groups']); + + if (isset($this->attributes['inherited_groups'])) { + $strategy->setInheritedGroups($this->attributes['inherited_groups']); + } + + $this->addExclusionStrategy($strategy); } if (isset($this->attributes['version'])) { @@ -204,6 +210,16 @@ public function enableMaxDepthChecks(): self return $this; } + /** + * @return $this + */ + public function enableInheritGroups(): self + { + $this->attributes['inherited_groups'] = true; + + return $this; + } + public function getFormat(): string { return $this->format; diff --git a/src/Exclusion/GroupsExclusionStrategy.php b/src/Exclusion/GroupsExclusionStrategy.php index c3f0a7d99..59a4fe6ec 100644 --- a/src/Exclusion/GroupsExclusionStrategy.php +++ b/src/Exclusion/GroupsExclusionStrategy.php @@ -22,6 +22,8 @@ final class GroupsExclusionStrategy implements ExclusionStrategyInterface */ private $nestedGroups = false; + private $inheritedGroups = false; + public function __construct(array $groups) { if (empty($groups)) { @@ -44,6 +46,13 @@ public function __construct(array $groups) } } + public function setInheritedGroups(bool $inheritedGroups): self + { + $this->inheritedGroups = $inheritedGroups; + + return $this; + } + public function shouldSkipClass(ClassMetadata $metadata, Context $navigatorContext): bool { return false; @@ -96,7 +105,7 @@ public function getGroupsFor(Context $navigatorContext): array foreach ($paths as $index => $path) { if (!array_key_exists($path, $groups)) { if ($index > 0) { - $groups = [self::DEFAULT_GROUP]; + $groups = $this->inheritedGroups ? $groups : [self::DEFAULT_GROUP]; } else { $groups = array_filter($groups, 'is_string') ?: [self::DEFAULT_GROUP]; } diff --git a/tests/Exclusion/GroupsExclusionStrategyTest.php b/tests/Exclusion/GroupsExclusionStrategyTest.php index c5b292f93..424e9890c 100644 --- a/tests/Exclusion/GroupsExclusionStrategyTest.php +++ b/tests/Exclusion/GroupsExclusionStrategyTest.php @@ -57,7 +57,7 @@ public function getExclusionRules() /** * @dataProvider getGroupsFor */ - public function testGroupsFor(array $groups, array $propsVisited, array $resultingGroups) + public function testGroupsFor(array $groups, array $propsVisited, bool $inheritGroups, array $resultingGroups) { $exclusion = new GroupsExclusionStrategy($groups); $context = SerializationContext::create(); @@ -67,6 +67,7 @@ public function testGroupsFor(array $groups, array $propsVisited, array $resulti $context->pushPropertyMetadata($metadata); } + $exclusion->setInheritedGroups($inheritGroups); $groupsFor = $exclusion->getGroupsFor($context); self::assertEquals($groupsFor, $resultingGroups); } @@ -74,20 +75,26 @@ public function testGroupsFor(array $groups, array $propsVisited, array $resulti public function getGroupsFor() { return [ - [['foo'], ['prop'], ['foo']], - [[], ['prop'], ['Default']], + [['foo'], ['prop'], false, ['foo']], + [[], ['prop'], false, ['Default']], - [['foo', 'prop' => ['bar']], ['prop'], ['bar']], - [['foo', 'prop' => ['bar']], ['prop2'], ['foo']], + [['foo', 'prop' => ['bar']], ['prop'], false, ['bar']], + [['foo', 'prop' => ['bar']], ['prop2'], false, ['foo']], - [['prop' => ['bar']],['prop2'],['Default']], + [['prop' => ['bar']],['prop2'], false, ['Default']], - [['foo', 'prop' => ['bar']], ['prop', 'prop2'], ['Default']], + [['foo', 'prop' => ['bar']], ['prop', 'prop2'], false, ['Default']], - [['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' => ['xx', 'prop2' => ['def'], 'prop3' => ['def']]], ['prop', 'prop2', 'propB'], false, ['Default']], + [['foo', 'prop' => ['xx', 'prop2' => ['def', 'prop3' => ['def']]]], ['prop', 'prop2'], false, ['def', 'prop3' => ['def']]], - [['foo', 'prop' => ['prop2' => ['prop3' => ['def']]]], ['prop', 'prop2'], ['Default', 'prop3' => ['def']]], + [['foo', 'prop' => ['prop2' => ['prop3' => ['def']]]], ['prop', 'prop2'], false, ['Default', 'prop3' => ['def']]], + + [['foo'], ['prop'], true, ['foo']], + [[], ['prop'], true, ['Default']], + [['foo', 'prop' => ['xx', 'prop2' => ['def'], 'prop3' => ['def']]], ['prop', 'prop2', 'propB'], true, ['def']], + [['foo', 'prop' => ['xx', 'prop2' => ['def', 'prop3' => ['def']]]], ['prop', 'prop2'], true, ['def', 'prop3' => ['def']]], + [['foo', 'prop' => ['prop2' => ['prop3' => ['def']]]], ['prop', 'prop2'], true, ['Default', 'prop3' => ['def']]], ]; } }