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. |
21
|
|
|
* |
22
|
|
|
* @author Kévin Dunglas <[email protected]> |
23
|
|
|
*/ |
24
|
|
|
final class ObjectNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface, CacheableSupportsMethodInterface |
25
|
|
|
{ |
26
|
|
|
// To not use the current normalizer |
27
|
|
|
const WONT_DENORMALIZE = 'dunglas_normalizer_wont_denormalize'; |
28
|
|
|
const TYPE_FIELD = '#type'; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* @var NormalizerInterface|DenormalizerInterface |
32
|
|
|
*/ |
33
|
|
|
private $serializer; |
34
|
|
|
private $objectNormalizer; |
35
|
|
|
|
36
|
|
|
public function __construct(NormalizerInterface $objectNormalizer) |
37
|
|
|
{ |
38
|
|
|
if (!$objectNormalizer instanceof DenormalizerInterface) { |
39
|
|
|
throw new \InvalidArgumentException(sprintf('The normalizer used must implement the "%s" interface.', DenormalizerInterface::class)); |
40
|
|
|
} |
41
|
|
|
|
42
|
|
|
$this->objectNormalizer = $objectNormalizer; |
43
|
|
|
} |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* {@inheritdoc} |
47
|
|
|
*/ |
48
|
|
|
public function normalize($object, $format = null, array $context = []) |
49
|
|
|
{ |
50
|
|
|
return \array_merge([self::TYPE_FIELD => ClassUtils::getClass($object)], $this->objectNormalizer->normalize($object, $format, $context)); |
51
|
|
|
} |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* {@inheritdoc} |
55
|
|
|
*/ |
56
|
|
|
public function supportsNormalization($data, $format = null) |
57
|
|
|
{ |
58
|
|
|
return \is_object($data); |
59
|
|
|
} |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* {@inheritdoc} |
63
|
|
|
*/ |
64
|
|
|
public function denormalize($data, $class, $format = null, array $context = []) |
65
|
|
|
{ |
66
|
|
|
if (!\is_iterable($data) || isset($context[self::WONT_DENORMALIZE]) || \is_object($data)) { |
67
|
|
|
return $data; |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
if (null !== $type = $this->extractType($data)) { |
71
|
|
|
return $this->denormalizeObject($data, $type, $format, $context); |
|
|
|
|
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
foreach ($data as $key => $value) { |
75
|
|
|
$data[$key] = $this->denormalizeValue($value, $class, $format, $context); |
|
|
|
|
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
return $data; |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* {@inheritdoc} |
83
|
|
|
*/ |
84
|
|
|
public function supportsDenormalization($data, $type, $format = null) |
85
|
|
|
{ |
86
|
|
|
return true; |
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
/** |
90
|
|
|
* {@inheritdoc} |
91
|
|
|
*/ |
92
|
|
|
public function setSerializer(SerializerInterface $serializer) |
93
|
|
|
{ |
94
|
|
|
if (!$serializer instanceof NormalizerInterface || !$serializer instanceof DenormalizerInterface) { |
95
|
|
|
throw new \InvalidArgumentException( |
96
|
|
|
sprintf('The injected serializer must implement "%s" and "%s".', NormalizerInterface::class, DenormalizerInterface::class) |
97
|
|
|
); |
98
|
|
|
} |
99
|
|
|
|
100
|
|
|
$this->serializer = $serializer; |
101
|
|
|
|
102
|
|
|
if ($this->objectNormalizer instanceof SerializerAwareInterface) { |
103
|
|
|
$this->objectNormalizer->setSerializer($serializer); |
104
|
|
|
} |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
/** |
108
|
|
|
* {@inheritdoc} |
109
|
|
|
*/ |
110
|
|
|
public function hasCacheableSupportsMethod(): bool |
111
|
|
|
{ |
112
|
|
|
return true; |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
/** |
116
|
|
|
* Converts data to $class' object if possible. |
117
|
|
|
* |
118
|
|
|
* @param array $data |
119
|
|
|
* @param string $class |
120
|
|
|
* @param null $format |
121
|
|
|
* @param array $context |
122
|
|
|
* |
123
|
|
|
* @return object|null |
124
|
|
|
*/ |
125
|
|
|
private function denormalizeObject(array $data, string $class, $format = null, array $context = []) |
126
|
|
|
{ |
127
|
|
|
// Try to denormalize in other normalizers. Necessary to avoid cycle |
128
|
|
|
$context[self::WONT_DENORMALIZE] = true; |
129
|
|
|
if (\is_object($denormalizedValue = $this->serializer->denormalize($data, $class, $format, $context))) { |
|
|
|
|
130
|
|
|
return $denormalizedValue; |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
// Denormalize in current default normalizer |
134
|
|
|
unset($context[self::WONT_DENORMALIZE]); |
135
|
|
|
|
136
|
|
|
foreach ($data as $key => $value) { |
137
|
|
|
$data[$key] = $this->denormalizeValue($value, $class, $format, $context); |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
return $this->objectNormalizer->denormalize($data, $class, $format, $context); |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* Convert raw value to normalized value - object or primitive type. |
145
|
|
|
* |
146
|
|
|
* @param mixed $value |
147
|
|
|
* @param string $class |
148
|
|
|
* @param null $format |
149
|
|
|
* @param array $context |
150
|
|
|
* |
151
|
|
|
* @return object|null |
152
|
|
|
*/ |
153
|
|
|
private function denormalizeValue($value, string $class, $format = null, array $context = []) |
154
|
|
|
{ |
155
|
|
|
if (\is_object($value)) { |
156
|
|
|
return $value; |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
return (null !== $type = $this->extractType($value)) |
160
|
|
|
? $this->denormalizeObject($value, $type, $format, $context) |
161
|
|
|
: $this->serializer->denormalize($value, $class, $format, $context); |
|
|
|
|
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* Grab class from array. |
166
|
|
|
* |
167
|
|
|
* @param $data |
168
|
|
|
* |
169
|
|
|
* @return string|null |
170
|
|
|
*/ |
171
|
|
|
private function extractType(&$data) |
172
|
|
|
{ |
173
|
|
|
if (!isset($data[self::TYPE_FIELD])) { |
174
|
|
|
return null; |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
$type = $data[self::TYPE_FIELD]; |
178
|
|
|
unset($data[self::TYPE_FIELD]); |
179
|
|
|
|
180
|
|
|
return $type; |
181
|
|
|
} |
182
|
|
|
} |
183
|
|
|
|
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.