Completed
Push — master ( 8bbc83...6e0e98 )
by Dominik
02:07
created

Denormalizer::createNewObject()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

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