Denormalizer   A
last analyzed

Complexity

Total Complexity 32

Size/Duplication

Total Lines 231
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 6
Bugs 0 Features 0
Metric Value
eloc 83
c 6
b 0
f 0
dl 0
loc 231
ccs 77
cts 77
cp 1
rs 9.84
wmc 32

11 Methods

Rating   Name   Duplication   Size   Complexity  
A isCompliant() 0 10 2
A getSubPathByName() 0 3 2
A handleNotAllowedAdditionalFields() 0 9 1
A isWithinGroup() 0 24 4
A resetMissingFields() 0 19 4
A __construct() 0 6 1
A getSubPathsByNames() 0 8 2
B denormalize() 0 49 9
A denormalizeField() 0 22 3
A createNewObject() 0 17 2
A getObjectMapping() 0 8 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 Chubbyphp\Deserialization\Policy\GroupPolicy;
13
use Psr\Log\LoggerInterface;
14
use Psr\Log\NullLogger;
15
16
final class Denormalizer implements DenormalizerInterface
17
{
18
    /**
19
     * @var DenormalizerObjectMappingRegistryInterface
20
     */
21
    private $denormalizerObjectMappingRegistry;
22
23
    /**
24
     * @var LoggerInterface
25
     */
26
    private $logger;
27
28
    public function __construct(
29
        DenormalizerObjectMappingRegistryInterface $denormalizerObjectMappingRegistry,
30
        LoggerInterface $logger = null
31
    ) {
32 14
        $this->denormalizerObjectMappingRegistry = $denormalizerObjectMappingRegistry;
33
        $this->logger = $logger ?? new NullLogger();
34
    }
35
36 14
    /**
37 14
     * @param object|string $object
38 14
     *
39
     * @throws DeserializerLogicException
40
     * @throws DeserializerRuntimeException
41
     *
42
     * @return object
43
     */
44
    public function denormalize($object, array $data, DenormalizerContextInterface $context = null, string $path = '')
45
    {
46
        $context = $context ?? DenormalizerContextBuilder::create()->getContext();
47
48
        $class = is_object($object) ? get_class($object) : $object;
49
        $objectMapping = $this->getObjectMapping($class);
50
51 14
        $type = null;
52
        if (isset($data['_type'])) {
53 14
            $type = $data['_type'];
54
55 14
            unset($data['_type']);
56 14
        }
57
58 13
        $isNew = false;
59 13
        if (!is_object($object)) {
60 2
            $isNew = true;
61
            $object = $this->createNewObject($objectMapping, $path, $type);
62 2
        }
63
64
        $missingFields = [];
65 13
        $additionalFields = array_flip(array_keys($data));
66 13
        foreach ($objectMapping->getDenormalizationFieldMappings($path, $type) as $denormalizationFieldMapping) {
67 10
            $name = $denormalizationFieldMapping->getName();
68 10
69
            if (!array_key_exists($name, $data)) {
70
                $missingFields[] = $name;
71 12
72 12
                continue;
73 12
            }
74 12
75
            $this->denormalizeField($context, $denormalizationFieldMapping, $path, $name, $data, $object);
76 12
77 11
            unset($additionalFields[$name]);
78
        }
79 11
80
        $allowedAdditionalFields = $context->getAllowedAdditionalFields();
81
82 10
        if (null !== $allowedAdditionalFields
83
            && [] !== $fields = array_diff(array_keys($additionalFields), $allowedAdditionalFields)
84 10
        ) {
85
            $this->handleNotAllowedAdditionalFields($path, $fields);
86
        }
87 12
88
        if (!$isNew) {
89 12
            $this->resetMissingFields($context, $objectMapping, $object, $missingFields, $path, $type);
90 12
        }
91
92 1
        return $object;
93
    }
94
95 11
    /**
96 3
     * @throws DeserializerLogicException
97
     */
98
    private function getObjectMapping(string $class): DenormalizationObjectMappingInterface
99 11
    {
100
        try {
101
            return $this->denormalizerObjectMappingRegistry->getObjectMapping($class);
102
        } catch (DeserializerLogicException $exception) {
103
            $this->logger->error('deserialize: {exception}', ['exception' => $exception->getMessage()]);
104
105
            throw $exception;
106
        }
107
    }
108
109 14
    /**
110
     * @return object
111
     */
112 14
    private function createNewObject(
113 1
        DenormalizationObjectMappingInterface $objectMapping,
114 1
        string $path,
115
        string $type = null
116 1
    ) {
117
        $factory = $objectMapping->getDenormalizationFactory($path, $type);
118
        $object = $factory();
119
120
        if (is_object($object)) {
121
            return $object;
122
        }
123
124
        $exception = DeserializerLogicException::createFactoryDoesNotReturnObject($path, gettype($object));
125
126
        $this->logger->error('deserialize: {exception}', ['exception' => $exception->getMessage()]);
127 10
128
        throw $exception;
129
    }
130
131
    /**
132 10
     * @param object $object
133 10
     */
134
    private function denormalizeField(
135 10
        DenormalizerContextInterface $context,
136 9
        DenormalizationFieldMappingInterface $denormalizationFieldMapping,
137
        string $path,
138
        string $name,
139 1
        array $data,
140
        $object
141 1
    ): void {
142
        if (!$this->isCompliant($context, $denormalizationFieldMapping, $object)) {
143 1
            return;
144
        }
145
146
        if (!$this->isWithinGroup($context, $denormalizationFieldMapping)) {
147
            return;
148
        }
149
150
        $subPath = $this->getSubPathByName($path, $name);
151
152
        $this->logger->info('deserialize: path {path}', ['path' => $subPath]);
153
154 10
        $fieldDenormalizer = $denormalizationFieldMapping->getFieldDenormalizer();
155
        $fieldDenormalizer->denormalizeField($subPath, $object, $data[$name], $context, $this);
156
    }
157
158
    private function handleNotAllowedAdditionalFields(string $path, array $names): void
159
    {
160
        $exception = DeserializerRuntimeException::createNotAllowedAdditionalFields(
161
            $this->getSubPathsByNames($path, $names)
162 10
        );
163 1
164
        $this->logger->notice('deserialize: {exception}', ['exception' => $exception->getMessage()]);
165
166 9
        throw $exception;
167 1
    }
168
169
    /**
170 8
     * @param object $object
171
     */
172 8
    private function isCompliant(
173
        DenormalizerContextInterface $context,
174 8
        DenormalizationFieldMappingInterface $mapping,
175 8
        $object
176 8
    ): bool {
177
        if (!is_callable([$mapping, 'getPolicy'])) {
178
            return true;
179
        }
180
181
        return $mapping->getPolicy()->isCompliant($context, $object);
0 ignored issues
show
Bug introduced by
The method getPolicy() does not exist on Chubbyphp\Deserializatio...onFieldMappingInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Chubbyphp\Deserializatio...onFieldMappingInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

181
        return $mapping->/** @scrutinizer ignore-call */ getPolicy()->isCompliant($context, $object);
Loading history...
182 1
    }
183
184 1
    private function isWithinGroup(
185 1
        DenormalizerContextInterface $context,
186
        DenormalizationFieldMappingInterface $fieldMapping
187
    ): bool {
188 1
        if ([] === $groups = $context->getGroups()) {
0 ignored issues
show
Deprecated Code introduced by
The function Chubbyphp\Deserializatio...tInterface::getGroups() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

188
        if ([] === $groups = /** @scrutinizer ignore-deprecated */ $context->getGroups()) {
Loading history...
189
            return true;
190 1
        }
191
192
        @trigger_error(
193
            sprintf(
194
                'Use "%s" instead of "%s::setGroups"',
195
                GroupPolicy::class,
196
                DenormalizerContextInterface::class
197
            ),
198
            E_USER_DEPRECATED
199
        );
200 10
201
        foreach ($fieldMapping->getGroups() as $group) {
0 ignored issues
show
Deprecated Code introduced by
The function Chubbyphp\Deserializatio...gInterface::getGroups() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

201
        foreach (/** @scrutinizer ignore-deprecated */ $fieldMapping->getGroups() as $group) {
Loading history...
202
            if (in_array($group, $groups, true)) {
203
                return true;
204
            }
205 10
        }
206 8
207
        return false;
208
    }
209 2
210
    private function getSubPathByName(string $path, string $name): string
211
    {
212
        return '' === $path ? $name : $path.'.'.$name;
213
    }
214
215
    private function getSubPathsByNames(string $path, array $names): array
216
    {
217
        $subPaths = [];
218 9
        foreach ($names as $name) {
219
            $subPaths[] = $this->getSubPathByName($path, $name);
220
        }
221
222 9
        return $subPaths;
223 7
    }
224
225
    /**
226 2
     * @param object $object
227 2
     */
228 2
    private function resetMissingFields(
229 2
        DenormalizerContextInterface $context,
230 2
        DenormalizationObjectMappingInterface $objectMapping,
231
        $object,
232 2
        array $missingFields,
233
        string $path,
234
        string $type = null
235 2
    ): void {
236 1
        if (!method_exists($context, 'isResetMissingFields') || !$context->isResetMissingFields()) {
237 1
            return;
238
        }
239
240
        $factory = $objectMapping->getDenormalizationFactory($path, $type);
241 1
242
        $newObject = $factory();
243
244
        foreach ($missingFields as $missingField) {
245
            $accessor = new PropertyAccessor($missingField);
246
            $accessor->setValue($object, $accessor->getValue($newObject));
247
        }
248
    }
249
}
250