Passed
Push — master ( 064bed...d815ea )
by Gerrit
04:18
created

ObjectMapping::revertValue()   B

Complexity

Conditions 11
Paths 47

Size

Total Lines 87
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 22.7392

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 11
eloc 41
c 2
b 1
f 0
nc 47
nop 2
dl 0
loc 87
ccs 20
cts 37
cp 0.5405
crap 22.7392
rs 7.3166

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Copyright (C) 2018 Gerrit Addiks.
4
 * This package (including this file) was released under the terms of the GPL-3.0.
5
 * You should have received a copy of the GNU General Public License along with this program.
6
 * If not, see <http://www.gnu.org/licenses/> or send me a mail so i can send you a copy.
7
 *
8
 * @license GPL-3.0
9
 *
10
 * @author Gerrit Addiks <[email protected]>
11
 */
12
13
namespace Addiks\RDMBundle\Mapping;
14
15
use Doctrine\DBAL\Schema\Column;
16
use Addiks\RDMBundle\Mapping\MappingInterface;
17
use Webmozart\Assert\Assert;
18
use Addiks\RDMBundle\Hydration\HydrationContextInterface;
19
use Addiks\RDMBundle\Mapping\CallDefinitionInterface;
20
use Addiks\RDMBundle\Exception\FailedRDMAssertionException;
21
use ReflectionClass;
22
use ReflectionProperty;
23
use ReflectionException;
24
use Symfony\Component\DependencyInjection\ContainerInterface;
25
use Doctrine\DBAL\Types\Type;
26
use Doctrine\DBAL\Connection;
27
28
final class ObjectMapping implements MappingInterface
29
{
30
31
    /**
32
     * @var class-string
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
33
     */
34
    private $className;
35
36
    /**
37
     * @var array<MappingInterface>
38
     */
39
    private $fieldMappings = array();
40
41
    /**
42
     * @var Column|null
43
     */
44
    private $column;
45
46
    /**
47
     * @var CallDefinitionInterface|null
48
     */
49
    private $factory;
50
51
    /**
52
     * @var CallDefinitionInterface|null
53
     */
54
    private $serializer;
55
56
    /**
57
     * @var string
58
     */
59
    private $origin;
60
61
    /**
62
     * @var string|null
63
     */
64
    private $id;
65
66
    /**
67
     * @var string|null
68
     */
69
    private $referencedId;
70
71
    /** @param class-string $className */
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
72 17
    public function __construct(
73
        string $className,
74
        array $fieldMappings,
75
        Column $column = null,
76
        string $origin = "undefined",
77
        CallDefinitionInterface $factory = null,
78
        CallDefinitionInterface $serializer = null,
79
        string $id = null,
80
        string $referencedId = null
81
    ) {
82 17
        $this->className = $className;
83 17
        $this->factory = $factory;
84 17
        $this->column = $column;
85 17
        $this->serializer = $serializer;
86 17
        $this->origin = $origin;
87 17
        $this->id = $id;
88 17
        $this->referencedId = $referencedId;
89
90 17
        foreach ($fieldMappings as $fieldName => $fieldMapping) {
91 17
            Assert::isInstanceOf($fieldMapping, MappingInterface::class);
92 17
            Assert::false(is_numeric($fieldName));
93
94 17
            $this->fieldMappings[$fieldName] = $fieldMapping;
95
        }
96
    }
97
98 1
    public function getClassName(): string
99
    {
100 1
        return $this->className;
101
    }
102
103 1
    public function getDBALColumn(): ?Column
104
    {
105 1
        return $this->column;
106
    }
107
108 1
    public function getFieldMappings(): array
109
    {
110 1
        return $this->fieldMappings;
111
    }
112
113 1
    public function describeOrigin(): string
114
    {
115 1
        return $this->origin;
116
    }
117
118 2
    public function collectDBALColumns(): array
119
    {
120
        /** @var array<Column> $additionalColumns */
121 2
        $additionalColumns = array();
122
123 2
        if ($this->column instanceof Column) {
124 1
            $additionalColumns[] = $this->column;
125
        }
126
127 2
        foreach ($this->fieldMappings as $fieldMapping) {
128
            /** @var MappingInterface $fieldMapping */
129
130 2
            $additionalColumns = array_merge(
131
                $additionalColumns,
132 2
                $fieldMapping->collectDBALColumns()
133
            );
134
        }
135
136 2
        return $additionalColumns;
137
    }
138
139 1
    public function getFactory(): ?CallDefinitionInterface
140
    {
141 1
        return $this->factory;
142
    }
143
144 1
    public function getSerializer(): ?CallDefinitionInterface
145
    {
146 1
        return $this->serializer;
147
    }
148
149 1
    public function getId(): ?string
150
    {
151 1
        return $this->id;
152
    }
153
154 1
    public function getReferencedId(): ?string
155
    {
156 1
        return $this->referencedId;
157
    }
158
159 3
    public function resolveValue(
160
        HydrationContextInterface $context,
161
        array $dataFromAdditionalColumns
162
    ) {
163
        /** @var object|null $object */
164 3
        $object = null;
165
166
        # During creation of an object, the class-name is on the top of that creation stack
167 3
        $context->pushOnObjectHydrationStack($this->className);
168
169 3
        $reflectionClass = new ReflectionClass($this->className);
170
171 3
        if (!empty($this->referencedId)) {
172
            $object = $context->getRegisteredValue($this->referencedId);
173
174 3
        } elseif ($this->factory instanceof CallDefinitionInterface) {
175
            /** @var array<string, string> $factoryData */
176 2
            $factoryData = $dataFromAdditionalColumns;
177
178 2
            if ($this->column instanceof Column && !array_key_exists("", $factoryData)) {
179
                /** @var Type $type */
180 1
                $type = $this->column->getType();
181
182
                /** @var string $columnName */
183 1
                $columnName = $this->column->getName();
184
185 1
                if (array_key_exists($columnName, $dataFromAdditionalColumns)) {
186
                    /** @var Connection $connection */
187
                    $connection = $context->getEntityManager()->getConnection();
188
189
                    if (!is_null($dataFromAdditionalColumns[$columnName])) {
190
                        $dataFromAdditionalColumns[$columnName] = $type->convertToPHPValue(
191
                            $dataFromAdditionalColumns[$columnName],
192
                            $connection->getDatabasePlatform()
193
                        );
194
                    }
195
196
                    $factoryData[""] = $dataFromAdditionalColumns[$columnName];
197
                }
198
            }
199
200 2
            $object = $this->factory->execute(
201
                $context,
202
                $factoryData
203
            );
204
205
        } else {
206 1
            if ($reflectionClass->isInstantiable()) {
207 1
                $object = $reflectionClass->newInstanceWithoutConstructor();
208
            }
209
        }
210
211 3
        if (is_object($object)) {
212
            // Replace the class-name with the created object on top of the hydration stack
213 3
            $context->popFromObjectHydrationStack();
214 3
            $context->pushOnObjectHydrationStack($object);
215
216 3
            if (!empty($this->id) && !$context->hasRegisteredValue($this->id)) {
217 2
                $context->registerValue($this->id, $object);
218
            }
219
220 3
            foreach ($this->fieldMappings as $fieldName => $fieldMapping) {
221
                /** @var MappingInterface $fieldMapping */
222
223
                /** @var mixed $fieldValue */
224 3
                $fieldValue = $fieldMapping->resolveValue(
225
                    $context,
226
                    $dataFromAdditionalColumns
227
                );
228
229
                /** @var ReflectionClass $propertyReflectionClass */
230 3
                $propertyReflectionClass = $reflectionClass;
231
232 3
                while (is_object($propertyReflectionClass) && !$propertyReflectionClass->hasProperty($fieldName)) {
233
                    $propertyReflectionClass = $propertyReflectionClass->getParentClass();
234
                }
235
236 3
                if (!is_object($propertyReflectionClass)) {
237
                    throw new ReflectionException(sprintf(
238
                        "Property '%s' does not exist on class '%s' as defined at '%s'",
239
                        $fieldName,
240
                        $reflectionClass->getName(),
241
                        $reflectionClass->getFileName()
242
                    ));
243
                }
244
245
                /** @var ReflectionProperty $reflectionProperty */
246 3
                $reflectionProperty = $propertyReflectionClass->getProperty($fieldName);
247
248 3
                $reflectionProperty->setAccessible(true);
249 3
                $reflectionProperty->setValue($object, $fieldValue);
250
            }
251
        }
252
253 3
        $context->popFromObjectHydrationStack();
254
255 3
        return $object;
256
    }
257
258 2
    public function revertValue(
259
        HydrationContextInterface $context,
260
        $valueFromEntityField
261
    ): array {
262
        /** @var array<scalar> $data */
263 2
        $data = array();
264
265 2
        $this->assertValue($context, [], $valueFromEntityField);
266
267 2
        $reflectionClass = new ReflectionClass($this->className);
268
269 2
        $context->pushOnObjectHydrationStack($valueFromEntityField);
270
271 2
        foreach ($this->fieldMappings as $fieldName => $fieldMapping) {
272
            /** @var MappingInterface $fieldMapping */
273
274
            /** @var ReflectionClass $propertyReflectionClass */
275 2
            $propertyReflectionClass = $reflectionClass;
276
277 2
            while (is_object($propertyReflectionClass) && !$propertyReflectionClass->hasProperty($fieldName)) {
278
                $propertyReflectionClass = $propertyReflectionClass->getParentClass();
279
            }
280
281 2
            if (!is_object($propertyReflectionClass)) {
282
                throw new ReflectionException(sprintf(
283
                    "Property %s does not exist in class %s. (Defined %s)",
284
                    $fieldName,
285
                    $this->className,
286
                    $this->origin
287
                ));
288
            }
289
290
            /** @var ReflectionProperty $reflectionProperty */
291 2
            $reflectionProperty = $propertyReflectionClass->getProperty($fieldName);
292 2
            $reflectionProperty->setAccessible(true);
293
294
            /** @var mixed $valueFromField */
295 2
            $valueFromField = null;
296
297 2
            if (is_object($valueFromEntityField) && $reflectionProperty->isInitialized($valueFromEntityField)) {
298 1
                $valueFromField = $reflectionProperty->getValue($valueFromEntityField);
299
            }
300
301
            /** @var array<string, mixed> $fieldData */
302 2
            $fieldData = $fieldMapping->revertValue(
303
                $context,
304
                $valueFromField
305
            );
306
307 2
            if (array_key_exists("", $fieldData)) {
308
                $fieldData[$fieldName] = $fieldData[""];
309
                unset($fieldData[""]);
310
            }
311
312 2
            $data = array_merge($data, $fieldData);
313
        }
314
315 2
        if ($this->serializer instanceof CallDefinitionInterface) {
316
            /** @var string $columnName */
317
            $columnName = '';
318
319
            if ($this->column instanceof Column) {
320
                $columnName = $this->column->getName();
321
            }
322
323
            $data[$columnName] = $this->serializer->execute(
324
                $context,
325
                array_merge($data, ['' => $valueFromEntityField])
326
            );
327
328
            if ($this->column instanceof Column) {
329
                /** @var Type $type */
330
                $type = $this->column->getType();
331
332
                /** @var Connection $connection */
333
                $connection = $context->getEntityManager()->getConnection();
334
335
                $data[$columnName] = $type->convertToDatabaseValue(
336
                    $data[$columnName],
337
                    $connection->getDatabasePlatform()
338
                );
339
            }
340
        }
341
342 2
        $context->popFromObjectHydrationStack();
343
344 2
        return $data;
345
    }
346
347 3
    public function assertValue(
348
        HydrationContextInterface $context,
349
        array $dataFromAdditionalColumns,
350
        $actualValue
351
    ): void {
352 3
        if (!is_null($actualValue) && !$actualValue instanceof $this->className) {
353 1
            throw FailedRDMAssertionException::expectedInstanceOf(
354 1
                $this->className,
355 1
                is_object($actualValue) ?get_class($actualValue) :gettype($actualValue),
356 1
                $this->origin
357
            );
358
        }
359
    }
360
361 1
    public function wakeUpMapping(ContainerInterface $container): void
362
    {
363 1
        if ($this->factory instanceof CallDefinitionInterface) {
364 1
            $this->factory->wakeUpCall($container);
365
        }
366
367 1
        if ($this->serializer instanceof CallDefinitionInterface) {
368 1
            $this->serializer->wakeUpCall($container);
369
        }
370
    }
371
372
}
373