Completed
Pull Request — master (#29)
by Dominik
06:47 queued 01:11
created

Denormalizer   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 274
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 12

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 32
lcom 1
cbo 12
dl 0
loc 274
ccs 86
cts 86
cp 1
rs 9.84
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
B denormalize() 0 49 9
A getObjectMapping() 0 10 2
A createNewObject() 0 18 2
A handleNotAllowedAdditionalFields() 0 10 1
A isWithinGroup() 0 16 4
A getSubPathByName() 0 4 2
A getSubPathsByNames() 0 9 2
A resetMissingFields() 0 21 4
A denormalizeField() 0 23 3
A isCompliant() 0 11 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Chubbyphp\Deserialization\Denormalizer;
6
7
use Chubbyphp\Deserialization\Accessor\PropertyAccessor;
8
use Chubbyphp\Deserialization\DeserializerLogicException;
9
use Chubbyphp\Deserialization\DeserializerRuntimeException;
10
use Chubbyphp\Deserialization\Mapping\DenormalizationFieldMappingInterface;
11
use Chubbyphp\Deserialization\Mapping\DenormalizationObjectMappingInterface;
12
use Psr\Log\LoggerInterface;
13
use Psr\Log\NullLogger;
14
15
final class Denormalizer implements DenormalizerInterface
16
{
17
    /**
18
     * @var DenormalizerObjectMappingRegistryInterface
19
     */
20
    private $denormalizerObjectMappingRegistry;
21
22
    /**
23
     * @var LoggerInterface
24
     */
25
    private $logger;
26
27
    /**
28
     * @param DenormalizerObjectMappingRegistryInterface $denormalizerObjectMappingRegistry
29
     * @param LoggerInterface|null                       $logger
30
     */
31 13
    public function __construct(
32
        DenormalizerObjectMappingRegistryInterface $denormalizerObjectMappingRegistry,
33
        LoggerInterface $logger = null
34
    ) {
35 13
        $this->denormalizerObjectMappingRegistry = $denormalizerObjectMappingRegistry;
36 13
        $this->logger = $logger ?? new NullLogger();
37 13
    }
38
39
    /**
40
     * @param object|string                     $object
41
     * @param array                             $data
42
     * @param DenormalizerContextInterface|null $context
43
     * @param string                            $path
44
     *
45
     * @return object
46
     *
47
     * @throws DeserializerLogicException
48
     * @throws DeserializerRuntimeException
49
     */
50 13
    public function denormalize($object, array $data, DenormalizerContextInterface $context = null, string $path = '')
51
    {
52 13
        $context = $context ?? DenormalizerContextBuilder::create()->getContext();
53
54 13
        $class = is_object($object) ? get_class($object) : $object;
55 13
        $objectMapping = $this->getObjectMapping($class);
56
57 12
        $type = null;
58 12
        if (isset($data['_type'])) {
59 2
            $type = $data['_type'];
60
61 2
            unset($data['_type']);
62
        }
63
64 12
        $isNew = false;
65 12
        if (!is_object($object)) {
66 10
            $isNew = true;
67 10
            $object = $this->createNewObject($objectMapping, $path, $type);
68
        }
69
70 11
        $missingFields = [];
71 11
        foreach ($objectMapping->getDenormalizationFieldMappings($path, $type) as $denormalizationFieldMapping) {
72 11
            $name = $denormalizationFieldMapping->getName();
73
74 11
            if (!array_key_exists($name, $data)) {
75 11
                $missingFields[] = $name;
76
77 11
                continue;
78
            }
79
80 9
            $this->denormalizeField($context, $denormalizationFieldMapping, $path, $name, $data, $object);
81
82 9
            unset($data[$name]);
83
        }
84
85 11
        $allowedAdditionalFields = $context->getAllowedAdditionalFields();
86
87 11
        if (null !== $allowedAdditionalFields
88 11
            && [] !== $fields = array_diff(array_keys($data), $allowedAdditionalFields)
89
        ) {
90 1
            $this->handleNotAllowedAdditionalFields($path, $fields);
91
        }
92
93 10
        if (!$isNew) {
94 2
            $this->resetMissingFields($context, $objectMapping, $object, $missingFields, $path, $type);
95
        }
96
97 10
        return $object;
98
    }
99
100
    /**
101
     * @param string $class
102
     *
103
     * @return DenormalizationObjectMappingInterface
104
     *
105
     * @throws DeserializerLogicException
106
     */
107 13
    private function getObjectMapping(string $class): DenormalizationObjectMappingInterface
108
    {
109
        try {
110 13
            return $this->denormalizerObjectMappingRegistry->getObjectMapping($class);
111 1
        } catch (DeserializerLogicException $exception) {
112 1
            $this->logger->error('deserialize: {exception}', ['exception' => $exception->getMessage()]);
113
114 1
            throw $exception;
115
        }
116
    }
117
118
    /**
119
     * @param DenormalizationObjectMappingInterface $objectMapping
120
     * @param string                                $path
121
     * @param string|null                           $type
122
     *
123
     * @return object
124
     */
125 10
    private function createNewObject(
126
        DenormalizationObjectMappingInterface $objectMapping,
127
        string $path,
128
        string $type = null
129
    ) {
130 10
        $factory = $objectMapping->getDenormalizationFactory($path, $type);
131 10
        $object = $factory();
132
133 10
        if (is_object($object)) {
134 9
            return $object;
135
        }
136
137 1
        $exception = DeserializerLogicException::createFactoryDoesNotReturnObject($path, gettype($object));
138
139 1
        $this->logger->error('deserialize: {exception}', ['exception' => $exception->getMessage()]);
140
141 1
        throw $exception;
142
    }
143
144
    /**
145
     * @param DenormalizerContextInterface         $context
146
     * @param DenormalizationFieldMappingInterface $denormalizationFieldMapping
147
     * @param string                               $path
148
     * @param array                                $data
149
     * @param object                               $object
150
     */
151 9
    private function denormalizeField(
152
        DenormalizerContextInterface $context,
153
        DenormalizationFieldMappingInterface $denormalizationFieldMapping,
154
        string $path,
155
        string $name,
156
        array $data,
157
        $object
158
    ) {
159 9
        if (!$this->isCompliant($context, $denormalizationFieldMapping, $object)) {
160 1
            return;
161
        }
162
163 8
        if (!$this->isWithinGroup($context, $denormalizationFieldMapping)) {
164 1
            return;
165
        }
166
167 7
        $subPath = $this->getSubPathByName($path, $name);
168
169 7
        $this->logger->info('deserialize: path {path}', ['path' => $subPath]);
170
171 7
        $fieldDenormalizer = $denormalizationFieldMapping->getFieldDenormalizer();
172 7
        $fieldDenormalizer->denormalizeField($subPath, $object, $data[$name], $context, $this);
173 7
    }
174
175
    /**
176
     * @param string $path
177
     * @param array  $names
178
     */
179 1
    private function handleNotAllowedAdditionalFields(string $path, array $names)
180
    {
181 1
        $exception = DeserializerRuntimeException::createNotAllowedAdditionalFields(
182 1
            $this->getSubPathsByNames($path, $names)
183
        );
184
185 1
        $this->logger->notice('deserialize: {exception}', ['exception' => $exception->getMessage()]);
186
187 1
        throw $exception;
188
    }
189
190
    /**
191
     * @param DenormalizerContextInterface         $context
192
     * @param DenormalizationFieldMappingInterface $mapping
193
     * @param object                               $object
194
     *
195
     * @return bool
196
     */
197 9
    private function isCompliant(
198
        DenormalizerContextInterface $context,
199
        DenormalizationFieldMappingInterface $mapping,
200
        $object
201
    ): bool {
202 9
        if (!is_callable([$mapping, 'getPolicy'])) {
203 7
            return true;
204
        }
205
206 2
        return $mapping->getPolicy()->isCompliant($context, $object);
207
    }
208
209
    /**
210
     * @param DenormalizerContextInterface         $context
211
     * @param DenormalizationFieldMappingInterface $fieldMapping
212
     *
213
     * @return bool
214
     */
215 8
    private function isWithinGroup(
216
        DenormalizerContextInterface $context,
217
        DenormalizationFieldMappingInterface $fieldMapping
218
    ): bool {
219 8
        if ([] === $groups = $context->getGroups()) {
0 ignored issues
show
Deprecated Code introduced by
The method Chubbyphp\Deserializatio...tInterface::getGroups() has been deprecated.

This method has been deprecated.

Loading history...
220 6
            return true;
221
        }
222
223 2
        foreach ($fieldMapping->getGroups() as $group) {
0 ignored issues
show
Deprecated Code introduced by
The method Chubbyphp\Deserializatio...gInterface::getGroups() has been deprecated.

This method has been deprecated.

Loading history...
224 1
            if (in_array($group, $groups, true)) {
225 1
                return true;
226
            }
227
        }
228
229 1
        return false;
230
    }
231
232
    /**
233
     * @param string $path
234
     * @param string $name
235
     *
236
     * @return string
237
     */
238 8
    private function getSubPathByName(string $path, string $name): string
239
    {
240 8
        return '' === $path ? $name : $path.'.'.$name;
241
    }
242
243
    /**
244
     * @param string $path
245
     * @param array  $names
246
     *
247
     * @return array
248
     */
249 1
    private function getSubPathsByNames(string $path, array $names): array
250
    {
251 1
        $subPaths = [];
252 1
        foreach ($names as $name) {
253 1
            $subPaths[] = $this->getSubPathByName($path, $name);
254
        }
255
256 1
        return $subPaths;
257
    }
258
259
    /**
260
     * @param DenormalizerContextInterface          $context
261
     * @param DenormalizationObjectMappingInterface $objectMapping
262
     * @param object                                $object
263
     * @param array                                 $missingFields
264
     * @param string                                $path
265
     * @param string|null                           $type
266
     */
267 2
    private function resetMissingFields(
268
        DenormalizerContextInterface $context,
269
        DenormalizationObjectMappingInterface $objectMapping,
270
        $object,
271
        array $missingFields,
272
        string $path,
273
        string $type = null
274
    ) {
275 2
        if (!method_exists($context, 'isResetMissingFields') || !$context->isResetMissingFields()) {
276 1
            return;
277
        }
278
279 1
        $factory = $objectMapping->getDenormalizationFactory($path, $type);
280
281 1
        $newObject = $factory();
282
283 1
        foreach ($missingFields as $missingField) {
284 1
            $accessor = new PropertyAccessor($missingField);
285 1
            $accessor->setValue($object, $accessor->getValue($newObject));
286
        }
287 1
    }
288
}
289