Passed
Push — master ( 96dfdb...d5f95b )
by Dominik
02:47
created

Denormalizer::resetMissingFields()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 21
ccs 9
cts 9
cp 1
rs 9.584
c 0
b 0
f 0
cc 4
nc 3
nop 6
crap 4
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 12
    public function __construct(
32
        DenormalizerObjectMappingRegistryInterface $denormalizerObjectMappingRegistry,
33
        LoggerInterface $logger = null
34
    ) {
35 12
        $this->denormalizerObjectMappingRegistry = $denormalizerObjectMappingRegistry;
36 12
        $this->logger = $logger ?? new NullLogger();
37 12
    }
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 12
    public function denormalize($object, array $data, DenormalizerContextInterface $context = null, string $path = '')
51
    {
52 12
        $context = $context ?? DenormalizerContextBuilder::create()->getContext();
53
54 12
        $class = is_object($object) ? get_class($object) : $object;
55 12
        $objectMapping = $this->getObjectMapping($class);
56
57 11
        $type = null;
58 11
        if (isset($data['_type'])) {
59 2
            $type = $data['_type'];
60
61 2
            unset($data['_type']);
62
        }
63
64 11
        $isNew = false;
65 11
        if (!is_object($object)) {
66 9
            $isNew = true;
67 9
            $object = $this->createNewObject($objectMapping, $path, $type);
68
        }
69
70 10
        $missingFields = [];
71 10
        foreach ($objectMapping->getDenormalizationFieldMappings($path, $type) as $denormalizationFieldMapping) {
72 10
            $name = $denormalizationFieldMapping->getName();
73
74 10
            if (!array_key_exists($name, $data)) {
75 10
                $missingFields[] = $name;
76
77 10
                continue;
78
            }
79
80 7
            $this->denormalizeField($context, $denormalizationFieldMapping, $path, $data, $object);
81
82 7
            unset($data[$name]);
83
        }
84
85 10
        if (null !== $context->getAllowedAdditionalFields()
86 10
            && [] !== $fields = array_diff(array_keys($data), $context->getAllowedAdditionalFields())
87
        ) {
88 2
            $this->handleNotAllowedAdditionalFields($path, $fields);
89
        }
90
91 8
        if (!$isNew) {
92 2
            $this->resetMissingFields($context, $objectMapping, $object, $missingFields, $path, $type);
93
        }
94
95 8
        return $object;
96
    }
97
98
    /**
99
     * @param string $class
100
     *
101
     * @return DenormalizationObjectMappingInterface
102
     *
103
     * @throws DeserializerLogicException
104
     */
105 12
    private function getObjectMapping(string $class): DenormalizationObjectMappingInterface
106
    {
107
        try {
108 12
            return $this->denormalizerObjectMappingRegistry->getObjectMapping($class);
109 1
        } catch (DeserializerLogicException $exception) {
110 1
            $this->logger->error('deserialize: {exception}', ['exception' => $exception->getMessage()]);
111
112 1
            throw $exception;
113
        }
114
    }
115
116
    /**
117
     * @param DenormalizationObjectMappingInterface $objectMapping
118
     * @param string                                $path
119
     * @param string|null                           $type
120
     *
121
     * @return object
122
     */
123 9
    private function createNewObject(
124
        DenormalizationObjectMappingInterface $objectMapping,
125
        string $path,
126
        string $type = null
127
    ) {
128 9
        $factory = $objectMapping->getDenormalizationFactory($path, $type);
129 9
        $object = $factory();
130
131 9
        if (is_object($object)) {
132 8
            return $object;
133
        }
134
135 1
        $exception = DeserializerLogicException::createFactoryDoesNotReturnObject($path, gettype($object));
136
137 1
        $this->logger->error('deserialize: {exception}', ['exception' => $exception->getMessage()]);
138
139 1
        throw $exception;
140
    }
141
142
    /**
143
     * @param DenormalizerContextInterface         $context
144
     * @param DenormalizationFieldMappingInterface $denormalizationFieldMapping
145
     * @param string                               $path
146
     * @param array                                $data
147
     * @param object                               $object
148
     */
149 7
    private function denormalizeField(
150
        DenormalizerContextInterface $context,
151
        DenormalizationFieldMappingInterface $denormalizationFieldMapping,
152
        string $path,
153
        array $data,
154
        $object
155
    ) {
156 7
        $fieldDenormalizer = $denormalizationFieldMapping->getFieldDenormalizer();
157
158 7
        if (!$this->isWithinGroup($context, $denormalizationFieldMapping)) {
159 1
            return;
160
        }
161
162 6
        $name = $denormalizationFieldMapping->getName();
163
164 6
        $subPath = $this->getSubPathByName($path, $name);
165
166 6
        $this->logger->info('deserialize: path {path}', ['path' => $subPath]);
167
168 6
        $fieldDenormalizer->denormalizeField($subPath, $object, $data[$name], $context, $this);
169 6
    }
170
171
    /**
172
     * @param string $path
173
     * @param array  $names
174
     */
175 2
    private function handleNotAllowedAdditionalFields(string $path, array $names)
176
    {
177 2
        $exception = DeserializerRuntimeException::createNotAllowedAdditionalFields(
178 2
            $this->getSubPathsByNames($path, $names)
179
        );
180
181 2
        $this->logger->notice('deserialize: {exception}', ['exception' => $exception->getMessage()]);
182
183 2
        throw $exception;
184
    }
185
186
    /**
187
     * @param DenormalizerContextInterface         $context
188
     * @param DenormalizationFieldMappingInterface $fieldMapping
189
     *
190
     * @return bool
191
     */
192 7
    private function isWithinGroup(
193
        DenormalizerContextInterface $context,
194
        DenormalizationFieldMappingInterface $fieldMapping
195
    ): bool {
196 7
        if ([] === $groups = $context->getGroups()) {
197 5
            return true;
198
        }
199
200 2
        foreach ($fieldMapping->getGroups() as $group) {
201 1
            if (in_array($group, $groups, true)) {
202 1
                return true;
203
            }
204
        }
205
206 1
        return false;
207
    }
208
209
    /**
210
     * @param string     $path
211
     * @param string|int $name
212
     *
213
     * @return string
214
     */
215 7
    private function getSubPathByName(string $path, $name): string
216
    {
217 7
        return '' === $path ? (string) $name : $path.'.'.$name;
218
    }
219
220
    /**
221
     * @param string $path
222
     * @param array  $names
223
     *
224
     * @return array
225
     */
226 2
    private function getSubPathsByNames(string $path, array $names): array
227
    {
228 2
        $subPaths = [];
229 2
        foreach ($names as $name) {
230 2
            $subPaths[] = $this->getSubPathByName($path, $name);
231
        }
232
233 2
        return $subPaths;
234
    }
235
236
    /**
237
     * @param DenormalizerContextInterface          $context
238
     * @param DenormalizationObjectMappingInterface $objectMapping
239
     * @param object                                $object
240
     * @param array                                 $missingFields
241
     * @param string                                $path
242
     * @param string|null                           $type
243
     */
244 2
    private function resetMissingFields(
245
        DenormalizerContextInterface $context,
246
        DenormalizationObjectMappingInterface $objectMapping,
247
        $object,
248
        array $missingFields,
249
        string $path,
250
        string $type = null
251
    ) {
252 2
        if (!method_exists($context, 'isResetMissingFields') || !$context->isResetMissingFields()) {
253 1
            return;
254
        }
255
256 1
        $factory = $objectMapping->getDenormalizationFactory($path, $type);
257
258 1
        $newObject = $factory();
259
260 1
        foreach ($missingFields as $missingField) {
261 1
            $accessor = new PropertyAccessor($missingField);
262 1
            $accessor->setValue($object, $accessor->getValue($newObject));
263
        }
264 1
    }
265
}
266