Test Failed
Push — master ( c747db...f5bd3a )
by Dominik
05:26
created

src/Denormalizer/Denormalizer.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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();
0 ignored issues
show
Documentation Bug introduced by
It seems like $logger ?? new \Psr\Log\NullLogger() can also be of type object<Psr\Log\NullLogger>. However, the property $logger is declared as type object<Psr\Log\LoggerInterface>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

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