Passed
Pull Request — master (#13)
by Dominik
03:26
created

Denormalizer   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 251
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 11

Test Coverage

Coverage 85.71%

Importance

Changes 0
Metric Value
wmc 31
lcom 1
cbo 11
dl 0
loc 251
ccs 72
cts 84
cp 0.8571
rs 9.8
c 0
b 0
f 0

10 Methods

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