1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Happyr\SerializerBundle\Normalizer; |
4
|
|
|
|
5
|
|
|
use Happyr\SerializerBundle\Annotation\ExclusionPolicy; |
6
|
|
|
use Happyr\SerializerBundle\Metadata\Metadata; |
7
|
|
|
use Happyr\SerializerBundle\PropertyManager\ReflectionPropertyAccess; |
8
|
|
|
use Happyr\SerializerBundle\PropertyManager\PropertyNameConverter; |
9
|
|
|
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; |
10
|
|
|
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; |
11
|
|
|
use Symfony\Component\Serializer\Normalizer\SerializerAwareNormalizer; |
12
|
|
|
use Symfony\Component\Serializer\Exception\LogicException; |
13
|
|
|
|
14
|
|
|
/** |
15
|
|
|
* @author Tobias Nyholm <[email protected]> |
16
|
|
|
*/ |
17
|
|
|
class MetadataAwareDenormalizer extends SerializerAwareNormalizer implements DenormalizerInterface |
|
|
|
|
18
|
|
|
{ |
19
|
|
|
use GroupValidationTrait; |
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* @var Metadata[] |
23
|
|
|
*/ |
24
|
|
|
private $metadata; |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* @var PropertyNameConverter |
28
|
|
|
*/ |
29
|
|
|
private $propertyNameConverter; |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* @param array $metadata |
33
|
|
|
* @param PropertyNameConverter $pnc |
34
|
|
|
*/ |
35
|
|
|
public function __construct(array $metadata, PropertyNameConverter $pnc) |
36
|
|
|
{ |
37
|
|
|
$this->metadata = $metadata; |
38
|
|
|
$this->propertyNameConverter = $pnc; |
39
|
|
|
} |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* {@inheritdoc} |
43
|
|
|
*/ |
44
|
|
|
public function denormalize($data, $class, $format = null, array $context = array()) |
45
|
|
|
{ |
46
|
|
|
$meta = $this->getMetadata($class); |
47
|
|
|
$normalizedData = (array) $data; |
48
|
|
|
$object = $this->getInstance($class, $context); |
49
|
|
|
|
50
|
|
|
foreach ($normalizedData as $attribute => $value) { |
51
|
|
|
if (null === $propertyName = $this->getPropertyName($meta, $attribute)) { |
|
|
|
|
52
|
|
|
// Name is invalid, skip this |
53
|
|
|
continue; |
54
|
|
|
} |
55
|
|
|
|
56
|
|
|
if (null !== $value && !is_scalar($value)) { |
57
|
|
|
if (!$this->serializer instanceof DenormalizerInterface) { |
58
|
|
|
throw new LogicException(sprintf('Cannot denormalize attribute "%s" because injected serializer is not a normalizer', $attribute)); |
59
|
|
|
} |
60
|
|
|
|
61
|
|
|
if (null !== $propertyType = $this->getPropertyType($meta, $propertyName)) { |
|
|
|
|
62
|
|
|
$value = $this->serializer->denormalize($value, $propertyType, $format, $context); |
63
|
|
|
} |
64
|
|
|
} |
65
|
|
|
|
66
|
|
|
$this->setPropertyValue($object, $meta, $propertyName, $value, $context); |
|
|
|
|
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
return $object; |
70
|
|
|
} |
71
|
|
|
|
72
|
|
|
/** |
73
|
|
|
* Set the property value. |
74
|
|
|
* |
75
|
|
|
* @param $object |
76
|
|
|
* @param array $meta |
77
|
|
|
* @param $propertyName |
78
|
|
|
* @param $value |
79
|
|
|
* @param array $context |
80
|
|
|
*/ |
81
|
|
|
private function setPropertyValue($object, array $meta, $propertyName, $value, array $context) |
82
|
|
|
{ |
83
|
|
|
if (!isset($meta['property'][$propertyName])) { |
84
|
|
|
$meta['property'][$propertyName] = []; |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
// Default exclusion policy is NONE |
88
|
|
|
$exclusionPolicy = isset($meta['class']['exclusion_policy']) ? $meta['class']['exclusion_policy'] : ExclusionPolicy::NONE; |
89
|
|
|
|
90
|
|
|
// If this property should be in the output |
91
|
|
|
$included = $exclusionPolicy === ExclusionPolicy::NONE; |
92
|
|
|
|
93
|
|
|
// If read_only is defined and true |
94
|
|
|
$readOnly = false; |
95
|
|
|
if (isset($meta['class']['read_only']) ? $meta['class']['read_only'] : false) { |
96
|
|
|
$readOnly = true; |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
$groups = ['Default']; |
100
|
|
|
$accessor = null; |
101
|
|
|
foreach ($meta['property'][$propertyName] as $name => $metaValue) { |
102
|
|
|
switch ($name) { |
103
|
|
|
case 'exclude': |
104
|
|
|
// Skip this |
105
|
|
|
return; |
106
|
|
|
case 'read_only': |
107
|
|
|
if ($metaValue === true) { |
108
|
|
|
return; |
109
|
|
|
} |
110
|
|
|
// If readOnly = false we should include this |
111
|
|
|
$readOnly = false; |
112
|
|
|
break; |
113
|
|
|
case 'expose': |
114
|
|
|
$included = true; |
115
|
|
|
break; |
116
|
|
|
case 'accessor': |
117
|
|
|
if (isset($metaValue['setter'])) { |
118
|
|
|
$accessor = $metaValue['setter']; |
119
|
|
|
} |
120
|
|
|
break; |
121
|
|
|
case 'groups': |
122
|
|
|
$groups = $metaValue; |
123
|
|
|
break; |
124
|
|
|
} |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
// Validate context groups |
128
|
|
|
if (!empty($context['groups'])) { |
129
|
|
|
$included = $this->includeBasedOnGroup($context, $groups); |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
if (!$included || $readOnly) { |
133
|
|
|
return; |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
if ($accessor) { |
137
|
|
|
$object->$accessor($value); |
138
|
|
|
} else { |
139
|
|
|
ReflectionPropertyAccess::set($object, $propertyName, $value); |
140
|
|
|
} |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* Get instance of the class. |
145
|
|
|
* |
146
|
|
|
* @param string $class |
147
|
|
|
* @param array $context |
148
|
|
|
* |
149
|
|
|
* @return object |
150
|
|
|
*/ |
151
|
|
|
private function getInstance($class, array $context) |
152
|
|
|
{ |
153
|
|
|
if ( |
154
|
|
|
isset($context[AbstractNormalizer::OBJECT_TO_POPULATE]) && |
155
|
|
|
is_object($context[AbstractNormalizer::OBJECT_TO_POPULATE]) && |
156
|
|
|
$context[AbstractNormalizer::OBJECT_TO_POPULATE] instanceof $class |
157
|
|
|
) { |
158
|
|
|
$object = $context[AbstractNormalizer::OBJECT_TO_POPULATE]; |
159
|
|
|
unset($context[AbstractNormalizer::OBJECT_TO_POPULATE]); |
160
|
|
|
|
161
|
|
|
return $object; |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
$reflectionClass = new \ReflectionClass($class); |
165
|
|
|
$constructor = $reflectionClass->getConstructor(); |
166
|
|
|
if (!$constructor) { |
167
|
|
|
return new $class(); |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
return $reflectionClass->newInstanceWithoutConstructor(); |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
/** |
174
|
|
|
* Get the property name for this normalized key name. This will aslo verify if the name is correct. |
175
|
|
|
* |
176
|
|
|
* @param array $rootMeta |
177
|
|
|
* @param string $serializedName |
178
|
|
|
* |
179
|
|
|
* @return string|null |
180
|
|
|
*/ |
181
|
|
|
private function getPropertyName($rootMeta, $serializedName) |
182
|
|
|
{ |
183
|
|
|
$propertyName = $this->propertyNameConverter->getPropertyName($rootMeta, $serializedName); |
184
|
|
|
|
185
|
|
|
$meta = isset($rootMeta['property'][$propertyName]) ? $rootMeta['property'][$propertyName] : []; |
186
|
|
|
$verify = $this->propertyNameConverter->getSerializedName($meta, $propertyName); |
187
|
|
|
|
188
|
|
|
if ($serializedName === $verify) { |
189
|
|
|
return $propertyName; |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
// The $serializedName was fake |
193
|
|
|
return; |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
/** |
197
|
|
|
* Get the type of this property. |
198
|
|
|
* |
199
|
|
|
* @param array $meta |
200
|
|
|
* @param string $name |
201
|
|
|
* |
202
|
|
|
* @return null|string |
203
|
|
|
*/ |
204
|
|
|
private function getPropertyType($meta, $name) |
205
|
|
|
{ |
206
|
|
|
foreach ($meta['property'][$name] as $metaName => $value) { |
207
|
|
|
if ($metaName === 'type') { |
208
|
|
|
return $value; |
209
|
|
|
} |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
return; |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
/** |
216
|
|
|
* @param mixed $data |
217
|
|
|
* @param string $type |
218
|
|
|
* @param null $format |
219
|
|
|
* |
220
|
|
|
* @return bool |
221
|
|
|
*/ |
222
|
|
|
public function supportsDenormalization($data, $type, $format = null) |
223
|
|
|
{ |
224
|
|
|
return isset($this->metadata[$type]); |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
/** |
228
|
|
|
* @param string $class |
229
|
|
|
* |
230
|
|
|
* @return Metadata|mixed |
231
|
|
|
*/ |
232
|
|
|
private function getMetadata($class) |
233
|
|
|
{ |
234
|
|
|
return $this->metadata[$class]; |
235
|
|
|
} |
236
|
|
|
} |
237
|
|
|
|
This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.