1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/* |
4
|
|
|
* (c) Kévin Dunglas <[email protected]> |
5
|
|
|
* |
6
|
|
|
* This source file is subject to the MIT license that is bundled |
7
|
|
|
* with this source code in the file LICENSE. |
8
|
|
|
*/ |
9
|
|
|
|
10
|
|
|
namespace Dunglas\DoctrineJsonOdm\Normalizer; |
11
|
|
|
|
12
|
|
|
use Doctrine\Common\Util\ClassUtils; |
13
|
|
|
use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface; |
14
|
|
|
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; |
15
|
|
|
use Symfony\Component\Serializer\Normalizer\NormalizerInterface; |
16
|
|
|
use Symfony\Component\Serializer\SerializerAwareInterface; |
17
|
|
|
use Symfony\Component\Serializer\SerializerInterface; |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* Transforms an object to an array with the following keys: |
21
|
|
|
* * _type: the class name |
22
|
|
|
* * _value: a representation of the values of the object. |
23
|
|
|
* |
24
|
|
|
* @author Kévin Dunglas <[email protected]> |
25
|
|
|
*/ |
26
|
|
|
final class ObjectNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface, CacheableSupportsMethodInterface |
27
|
|
|
{ |
28
|
|
|
const WONT_NORMALIZE = 'wont_normalize'; |
29
|
|
|
const TYPE_FIELD = '#type'; |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* @var NormalizerInterface|DenormalizerInterface |
33
|
|
|
*/ |
34
|
|
|
private $serializer; |
35
|
|
|
private $objectNormalizer; |
36
|
|
|
|
37
|
|
|
public function __construct(NormalizerInterface $objectNormalizer) |
38
|
|
|
{ |
39
|
|
|
if (!$objectNormalizer instanceof DenormalizerInterface) { |
40
|
|
|
throw new \InvalidArgumentException(sprintf('The normalizer used must implement the "%s" interface.', DenormalizerInterface::class)); |
41
|
|
|
} |
42
|
|
|
|
43
|
|
|
$this->objectNormalizer = $objectNormalizer; |
44
|
|
|
} |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* {@inheritdoc} |
48
|
|
|
*/ |
49
|
|
|
public function normalize($object, $format = null, array $context = []) |
50
|
|
|
{ |
51
|
|
|
return \array_merge([self::TYPE_FIELD => ClassUtils::getClass($object)], $this->objectNormalizer->normalize($object, $format, $context)); |
52
|
|
|
} |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* {@inheritdoc} |
56
|
|
|
*/ |
57
|
|
|
public function supportsNormalization($data, $format = null) |
58
|
|
|
{ |
59
|
|
|
return \is_object($data); |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* {@inheritdoc} |
64
|
|
|
*/ |
65
|
|
|
public function denormalize($data, $class, $format = null, array $context = []) |
66
|
|
|
{ |
67
|
|
|
if (isset($context[self::WONT_NORMALIZE]) || \is_object($data) || !\is_iterable($data)) { |
68
|
|
|
return $data; |
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
if (null !== $type = $this->extractType($data)) { |
72
|
|
|
return $this->denormalizeObject($data, $type, $format, $context); |
|
|
|
|
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
foreach ($data as $key => $value) { |
76
|
|
|
if (!\is_object($value)) { |
77
|
|
|
$data[$key] = $this->denormalizeValue($value, $class, $format, $context); |
|
|
|
|
78
|
|
|
} |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
return $data; |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* {@inheritdoc} |
86
|
|
|
*/ |
87
|
|
|
public function supportsDenormalization($data, $type, $format = null) |
88
|
|
|
{ |
89
|
|
|
return true; |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
/** |
93
|
|
|
* {@inheritdoc} |
94
|
|
|
*/ |
95
|
|
|
public function setSerializer(SerializerInterface $serializer) |
96
|
|
|
{ |
97
|
|
|
if (!$serializer instanceof NormalizerInterface || !$serializer instanceof DenormalizerInterface) { |
98
|
|
|
throw new \InvalidArgumentException( |
99
|
|
|
sprintf('The injected serializer must implement "%s" and "%s".', NormalizerInterface::class, DenormalizerInterface::class) |
100
|
|
|
); |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
$this->serializer = $serializer; |
104
|
|
|
|
105
|
|
|
if ($this->objectNormalizer instanceof SerializerAwareInterface) { |
106
|
|
|
$this->objectNormalizer->setSerializer($serializer); |
107
|
|
|
} |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* {@inheritdoc} |
112
|
|
|
*/ |
113
|
|
|
public function hasCacheableSupportsMethod(): bool |
114
|
|
|
{ |
115
|
|
|
return true; |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
/** |
119
|
|
|
* Converts data to $class' object if possible. |
120
|
|
|
* |
121
|
|
|
* @param array $data |
122
|
|
|
* @param string $class |
123
|
|
|
* @param null $format |
124
|
|
|
* @param array $context |
125
|
|
|
* |
126
|
|
|
* @return object|null |
127
|
|
|
*/ |
128
|
|
|
private function denormalizeObject(array $data, string $class, $format = null, array $context = []) |
129
|
|
|
{ |
130
|
|
|
return $this->denormalizeObjectInOtherNormalizer($data, $class, $format, $context) |
131
|
|
|
?? $this->denormalizeObjectInDefaultObjectNormalizer($data, $class, $format, $context); |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
/** |
135
|
|
|
* Tries to convert data to $class' object not using current normalizer. |
136
|
|
|
* This is useful if you have your own normalizers - they will have priority over this one. |
137
|
|
|
* |
138
|
|
|
* @param array $data |
139
|
|
|
* @param string $class |
140
|
|
|
* @param null $format |
141
|
|
|
* @param array $context |
142
|
|
|
* |
143
|
|
|
* @return object|null |
144
|
|
|
*/ |
145
|
|
|
private function denormalizeObjectInOtherNormalizer(array $data, string $class, $format = null, array $context = []) |
146
|
|
|
{ |
147
|
|
|
$context[self::WONT_NORMALIZE] = true; |
148
|
|
|
|
149
|
|
|
return \is_object($object = $this->serializer->denormalize($data, $class, $format, $context)) ? $object : null; |
|
|
|
|
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
/** |
153
|
|
|
* Default denormalization $data to class using symfony's object normalizer. |
154
|
|
|
* |
155
|
|
|
* @param array $data |
156
|
|
|
* @param string $class |
157
|
|
|
* @param null $format |
158
|
|
|
* @param array $context |
159
|
|
|
* |
160
|
|
|
* @return object |
161
|
|
|
*/ |
162
|
|
|
private function denormalizeObjectInDefaultObjectNormalizer(array $data, string $class, $format = null, array $context = []) |
163
|
|
|
{ |
164
|
|
|
foreach ($data as $key => $value) { |
165
|
|
|
$data[$key] = $this->denormalizeValue($value, $class, $format, $context); |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
return $this->objectNormalizer->denormalize($data, $class, $format, $context); |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
/** |
172
|
|
|
* Convert raw value to normalized value - object or primitive type. |
173
|
|
|
* |
174
|
|
|
* @param mixed $value |
175
|
|
|
* @param string $class |
176
|
|
|
* @param null $format |
177
|
|
|
* @param array $context |
178
|
|
|
* |
179
|
|
|
* @return object|null |
180
|
|
|
*/ |
181
|
|
|
private function denormalizeValue($value, string $class, $format = null, array $context = []) |
182
|
|
|
{ |
183
|
|
|
return (null !== $type = $this->extractType($value)) |
184
|
|
|
? $this->denormalizeObject($value, $type, $format, $context) |
185
|
|
|
: $this->serializer->denormalize($value, $class, $format, $context); |
|
|
|
|
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
/** |
189
|
|
|
* Grab class from array. |
190
|
|
|
* |
191
|
|
|
* @param $data |
192
|
|
|
* |
193
|
|
|
* @return string|null |
194
|
|
|
*/ |
195
|
|
|
private function extractType(&$data): ?string |
196
|
|
|
{ |
197
|
|
|
if (!$this->isObjectArray($data)) { |
198
|
|
|
return null; |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
$type = $data[self::TYPE_FIELD]; |
202
|
|
|
unset($data[self::TYPE_FIELD]); |
203
|
|
|
|
204
|
|
|
return $type; |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
/** |
208
|
|
|
* To determine is passed data is a representation of some object. |
209
|
|
|
* |
210
|
|
|
* @param $data |
211
|
|
|
* |
212
|
|
|
* @return bool |
213
|
|
|
*/ |
214
|
|
|
private function isObjectArray($data): bool |
215
|
|
|
{ |
216
|
|
|
return isset($data[self::TYPE_FIELD]); |
217
|
|
|
} |
218
|
|
|
} |
219
|
|
|
|
This check looks at variables that have been passed in as parameters and are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.