Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 7229fa1

Browse filesBrowse files
ogizanagifabpot
authored andcommitted
[Serializer] Allow to provide (de)normalization context in mapping
1 parent d9f490a commit 7229fa1
Copy full SHA for 7229fa1

27 files changed

+1183
-17
lines changed
+93Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Serializer\Annotation;
13+
14+
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
15+
16+
/**
17+
* Annotation class for @Context().
18+
*
19+
* @Annotation
20+
* @Target({"PROPERTY", "METHOD"})
21+
*
22+
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
23+
*/
24+
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
25+
final class Context
26+
{
27+
private $context;
28+
private $normalizationContext;
29+
private $denormalizationContext;
30+
private $groups;
31+
32+
/**
33+
* @throws InvalidArgumentException
34+
*/
35+
public function __construct(array $options = [], array $context = [], array $normalizationContext = [], array $denormalizationContext = [], array $groups = [])
36+
{
37+
if (!$context) {
38+
if (!array_intersect((array_keys($options)), ['normalizationContext', 'groups', 'context', 'value', 'denormalizationContext'])) {
39+
// gracefully supports context as first, unnamed attribute argument if it cannot be confused with Doctrine-style options
40+
$context = $options;
41+
} else {
42+
// If at least one of the options match, it's likely to be Doctrine-style options. Search for the context inside:
43+
$context = $options['value'] ?? $options['context'] ?? [];
44+
}
45+
}
46+
47+
$normalizationContext = $options['normalizationContext'] ?? $normalizationContext;
48+
$denormalizationContext = $options['denormalizationContext'] ?? $denormalizationContext;
49+
50+
foreach (compact(['context', 'normalizationContext', 'denormalizationContext']) as $key => $value) {
51+
if (!\is_array($value)) {
52+
throw new InvalidArgumentException(sprintf('Option "%s" of annotation "%s" must be an array.', $key, static::class));
53+
}
54+
}
55+
56+
if (!$context && !$normalizationContext && !$denormalizationContext) {
57+
throw new InvalidArgumentException(sprintf('At least one of the "context", "normalizationContext", or "denormalizationContext" options of annotation "%s" must be provided as a non-empty array.', static::class));
58+
}
59+
60+
$groups = (array) ($options['groups'] ?? $groups);
61+
62+
foreach ($groups as $group) {
63+
if (!\is_string($group)) {
64+
throw new InvalidArgumentException(sprintf('Parameter "groups" of annotation "%s" must be a string or an array of strings. Got "%s".', static::class, get_debug_type($group)));
65+
}
66+
}
67+
68+
$this->context = $context;
69+
$this->normalizationContext = $normalizationContext;
70+
$this->denormalizationContext = $denormalizationContext;
71+
$this->groups = $groups;
72+
}
73+
74+
public function getContext(): array
75+
{
76+
return $this->context;
77+
}
78+
79+
public function getNormalizationContext(): array
80+
{
81+
return $this->normalizationContext;
82+
}
83+
84+
public function getDenormalizationContext(): array
85+
{
86+
return $this->denormalizationContext;
87+
}
88+
89+
public function getGroups(): array
90+
{
91+
return $this->groups;
92+
}
93+
}

‎src/Symfony/Component/Serializer/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
5.3
55
---
66

7+
* Add the ability to provide (de)normalization context using metadata (e.g. `@Symfony\Component\Serializer\Annotation\Context`)
78
* deprecated `ArrayDenormalizer::setSerializer()`, call `setDenormalizer()` instead.
89
* added normalization formats to `UidNormalizer`
910

‎src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php
+95-1Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,24 @@ class AttributeMetadata implements AttributeMetadataInterface
5959
*/
6060
public $ignore = false;
6161

62+
/**
63+
* @var array[] Normalization contexts per group name ("*" applies to all groups)
64+
*
65+
* @internal This property is public in order to reduce the size of the
66+
* class' serialized representation. Do not access it. Use
67+
* {@link getNormalizationContexts()} instead.
68+
*/
69+
public $normalizationContexts = [];
70+
71+
/**
72+
* @var array[] Denormalization contexts per group name ("*" applies to all groups)
73+
*
74+
* @internal This property is public in order to reduce the size of the
75+
* class' serialized representation. Do not access it. Use
76+
* {@link getDenormalizationContexts()} instead.
77+
*/
78+
public $denormalizationContexts = [];
79+
6280
public function __construct(string $name)
6381
{
6482
$this->name = $name;
@@ -138,6 +156,76 @@ public function isIgnored(): bool
138156
return $this->ignore;
139157
}
140158

159+
/**
160+
* {@inheritdoc}
161+
*/
162+
public function getNormalizationContexts(): array
163+
{
164+
return $this->normalizationContexts;
165+
}
166+
167+
/**
168+
* {@inheritdoc}
169+
*/
170+
public function getNormalizationContextForGroups(array $groups): array
171+
{
172+
$contexts = [];
173+
foreach ($groups as $group) {
174+
$contexts[] = $this->normalizationContexts[$group] ?? [];
175+
}
176+
177+
return array_merge($this->normalizationContexts['*'] ?? [], ...$contexts);
178+
}
179+
180+
/**
181+
* {@inheritdoc}
182+
*/
183+
public function setNormalizationContextForGroups(array $context, array $groups = []): void
184+
{
185+
if (!$groups) {
186+
$this->normalizationContexts['*'] = $context;
187+
}
188+
189+
foreach ($groups as $group) {
190+
$this->normalizationContexts[$group] = $context;
191+
}
192+
}
193+
194+
/**
195+
* {@inheritdoc}
196+
*/
197+
public function getDenormalizationContexts(): array
198+
{
199+
return $this->denormalizationContexts;
200+
}
201+
202+
/**
203+
* {@inheritdoc}
204+
*/
205+
public function getDenormalizationContextForGroups(array $groups): array
206+
{
207+
$contexts = [];
208+
foreach ($groups as $group) {
209+
$contexts[] = $this->denormalizationContexts[$group] ?? [];
210+
}
211+
212+
return array_merge($this->denormalizationContexts['*'] ?? [], ...$contexts);
213+
}
214+
215+
/**
216+
* {@inheritdoc}
217+
*/
218+
public function setDenormalizationContextForGroups(array $context, array $groups = []): void
219+
{
220+
if (!$groups) {
221+
$this->denormalizationContexts['*'] = $context;
222+
}
223+
224+
foreach ($groups as $group) {
225+
$this->denormalizationContexts[$group] = $context;
226+
}
227+
}
228+
141229
/**
142230
* {@inheritdoc}
143231
*/
@@ -157,6 +245,12 @@ public function merge(AttributeMetadataInterface $attributeMetadata)
157245
$this->serializedName = $attributeMetadata->getSerializedName();
158246
}
159247

248+
// Overwrite only if both contexts are empty
249+
if (!$this->normalizationContexts && !$this->denormalizationContexts) {
250+
$this->normalizationContexts = $attributeMetadata->getNormalizationContexts();
251+
$this->denormalizationContexts = $attributeMetadata->getDenormalizationContexts();
252+
}
253+
160254
if ($ignore = $attributeMetadata->isIgnored()) {
161255
$this->ignore = $ignore;
162256
}
@@ -169,6 +263,6 @@ public function merge(AttributeMetadataInterface $attributeMetadata)
169263
*/
170264
public function __sleep()
171265
{
172-
return ['name', 'groups', 'maxDepth', 'serializedName', 'ignore'];
266+
return ['name', 'groups', 'maxDepth', 'serializedName', 'ignore', 'normalizationContexts', 'denormalizationContexts'];
173267
}
174268
}

‎src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php
+30Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,34 @@ public function isIgnored(): bool;
7575
* Merges an {@see AttributeMetadataInterface} with in the current one.
7676
*/
7777
public function merge(self $attributeMetadata);
78+
79+
/**
80+
* Gets all the normalization contexts per group ("*" being the base context applied to all groups).
81+
*/
82+
public function getNormalizationContexts(): array;
83+
84+
/**
85+
* Gets the computed normalization contexts for given groups.
86+
*/
87+
public function getNormalizationContextForGroups(array $groups): array;
88+
89+
/**
90+
* Sets the normalization context for given groups.
91+
*/
92+
public function setNormalizationContextForGroups(array $context, array $groups = []): void;
93+
94+
/**
95+
* Gets all the denormalization contexts per group ("*" being the base context applied to all groups).
96+
*/
97+
public function getDenormalizationContexts(): array;
98+
99+
/**
100+
* Gets the computed denormalization contexts for given groups.
101+
*/
102+
public function getDenormalizationContextForGroups(array $groups): array;
103+
104+
/**
105+
* Sets the denormalization context for given groups.
106+
*/
107+
public function setDenormalizationContextForGroups(array $context, array $groups = []): void;
78108
}

‎src/Symfony/Component/Serializer/Mapping/Loader/AnnotationLoader.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Mapping/Loader/AnnotationLoader.php
+27Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@
1212
namespace Symfony\Component\Serializer\Mapping\Loader;
1313

1414
use Doctrine\Common\Annotations\Reader;
15+
use Symfony\Component\Serializer\Annotation\Context;
1516
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
1617
use Symfony\Component\Serializer\Annotation\Groups;
1718
use Symfony\Component\Serializer\Annotation\Ignore;
1819
use Symfony\Component\Serializer\Annotation\MaxDepth;
1920
use Symfony\Component\Serializer\Annotation\SerializedName;
2021
use Symfony\Component\Serializer\Exception\MappingException;
2122
use Symfony\Component\Serializer\Mapping\AttributeMetadata;
23+
use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface;
2224
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping;
2325
use Symfony\Component\Serializer\Mapping\ClassMetadataInterface;
2426

@@ -36,6 +38,7 @@ class AnnotationLoader implements LoaderInterface
3638
Ignore::class => true,
3739
MaxDepth::class => true,
3840
SerializedName::class => true,
41+
Context::class => true,
3942
];
4043

4144
private $reader;
@@ -83,6 +86,8 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata)
8386
$attributesMetadata[$property->name]->setSerializedName($annotation->getSerializedName());
8487
} elseif ($annotation instanceof Ignore) {
8588
$attributesMetadata[$property->name]->setIgnore(true);
89+
} elseif ($annotation instanceof Context) {
90+
$this->setAttributeContextsForGroups($annotation, $attributesMetadata[$property->name]);
8691
}
8792

8893
$loaded = true;
@@ -130,6 +135,12 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata)
130135
$attributeMetadata->setSerializedName($annotation->getSerializedName());
131136
} elseif ($annotation instanceof Ignore) {
132137
$attributeMetadata->setIgnore(true);
138+
} elseif ($annotation instanceof Context) {
139+
if (!$accessorOrMutator) {
140+
throw new MappingException(sprintf('Context on "%s::%s()" cannot be added. Context can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name));
141+
}
142+
143+
$this->setAttributeContextsForGroups($annotation, $attributeMetadata);
133144
}
134145

135146
$loaded = true;
@@ -166,4 +177,20 @@ public function loadAnnotations(object $reflector): iterable
166177
yield from $this->reader->getPropertyAnnotations($reflector);
167178
}
168179
}
180+
181+
private function setAttributeContextsForGroups(Context $annotation, AttributeMetadataInterface $attributeMetadata): void
182+
{
183+
if ($annotation->getContext()) {
184+
$attributeMetadata->setNormalizationContextForGroups($annotation->getContext(), $annotation->getGroups());
185+
$attributeMetadata->setDenormalizationContextForGroups($annotation->getContext(), $annotation->getGroups());
186+
}
187+
188+
if ($annotation->getNormalizationContext()) {
189+
$attributeMetadata->setNormalizationContextForGroups($annotation->getNormalizationContext(), $annotation->getGroups());
190+
}
191+
192+
if ($annotation->getDenormalizationContext()) {
193+
$attributeMetadata->setDenormalizationContextForGroups($annotation->getDenormalizationContext(), $annotation->getGroups());
194+
}
195+
}
169196
}

‎src/Symfony/Component/Serializer/Mapping/Loader/XmlFileLoader.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Mapping/Loader/XmlFileLoader.php
+44Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,25 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata)
7474
if (isset($attribute['ignore'])) {
7575
$attributeMetadata->setIgnore((bool) $attribute['ignore']);
7676
}
77+
78+
foreach ($attribute->context as $node) {
79+
$groups = (array) $node->group;
80+
$context = $this->parseContext($node->entry);
81+
$attributeMetadata->setNormalizationContextForGroups($context, $groups);
82+
$attributeMetadata->setDenormalizationContextForGroups($context, $groups);
83+
}
84+
85+
foreach ($attribute->normalization_context as $node) {
86+
$groups = (array) $node->group;
87+
$context = $this->parseContext($node->entry);
88+
$attributeMetadata->setNormalizationContextForGroups($context, $groups);
89+
}
90+
91+
foreach ($attribute->denormalization_context as $node) {
92+
$groups = (array) $node->group;
93+
$context = $this->parseContext($node->entry);
94+
$attributeMetadata->setDenormalizationContextForGroups($context, $groups);
95+
}
7796
}
7897

7998
if (isset($xml->{'discriminator-map'})) {
@@ -136,4 +155,29 @@ private function getClassesFromXml(): array
136155

137156
return $classes;
138157
}
158+
159+
private function parseContext(\SimpleXMLElement $nodes): array
160+
{
161+
$context = [];
162+
163+
foreach ($nodes as $node) {
164+
if (\count($node) > 0) {
165+
if (\count($node->entry) > 0) {
166+
$value = $this->parseContext($node->entry);
167+
} else {
168+
$value = [];
169+
}
170+
} else {
171+
$value = XmlUtils::phpize($node);
172+
}
173+
174+
if (isset($node['name'])) {
175+
$context[(string) $node['name']] = $value;
176+
} else {
177+
$context[] = $value;
178+
}
179+
}
180+
181+
return $context;
182+
}
139183
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.