Passed
Pull Request — master (#24)
by Dominik
02:13
created

Denormalizer::resetMissingFields()   B

Complexity

Conditions 7
Paths 4

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 7

Importance

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