Passed
Push — master ( ad635e...933d9a )
by Dominik
02:52
created

Denormalizer::checkDataContainsNumericKey()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 20
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4

Importance

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