Passed
Push — master ( ada1ad...0bd463 )
by Gerrit
13:02
created

ObjectMapping::resolveValue()   C

Complexity

Conditions 17
Paths 63

Size

Total Lines 104
Code Lines 50

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 22.5816

Importance

Changes 3
Bugs 3 Features 0
Metric Value
cc 17
eloc 50
c 3
b 3
f 0
nc 63
nop 2
dl 0
loc 104
ccs 30
cts 41
cp 0.7317
crap 22.5816
rs 5.2166

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
use BackedEnum;
0 ignored issues
show
Bug introduced by
The type BackedEnum was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
28
use UnitEnum;
0 ignored issues
show
Bug introduced by
The type UnitEnum was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
29
30
final class ObjectMapping implements MappingInterface
31
{
32
33
    /**
34
     * @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...
35
     */
36
    private $className;
37
38
    /**
39
     * @var array<MappingInterface>
40
     */
41
    private $fieldMappings = array();
42
43
    /**
44
     * @var Column|null
45
     */
46
    private $column;
47
48
    /**
49
     * @var CallDefinitionInterface|null
50
     */
51
    private $factory;
52
53
    /**
54
     * @var CallDefinitionInterface|null
55
     */
56
    private $serializer;
57
58
    /**
59
     * @var string
60
     */
61
    private $origin;
62
63
    /**
64
     * @var string|null
65
     */
66
    private $id;
67
68
    /**
69
     * @var string|null
70
     */
71
    private $referencedId;
72 17
73
    /** @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...
74
    public function __construct(
75
        string $className,
76
        array $fieldMappings,
77
        Column $column = null,
78
        string $origin = "undefined",
79
        CallDefinitionInterface $factory = null,
80
        CallDefinitionInterface $serializer = null,
81
        string $id = null,
82 17
        string $referencedId = null
83 17
    ) {
84 17
        $this->className = $className;
85 17
        $this->factory = $factory;
86 17
        $this->column = $column;
87 17
        $this->serializer = $serializer;
88 17
        $this->origin = $origin;
89
        $this->id = $id;
90 17
        $this->referencedId = $referencedId;
91 17
92 17
        foreach ($fieldMappings as $fieldName => $fieldMapping) {
93
            Assert::isInstanceOf($fieldMapping, MappingInterface::class);
94 17
            Assert::false(is_numeric($fieldName));
95
96
            $this->fieldMappings[$fieldName] = $fieldMapping;
97
        }
98 1
    }
99
100 1
    public function getClassName(): string
101
    {
102
        return $this->className;
103 1
    }
104
105 1
    public function getDBALColumn(): ?Column
106
    {
107
        return $this->column;
108 1
    }
109
110 1
    public function getFieldMappings(): array
111
    {
112
        return $this->fieldMappings;
113 1
    }
114
115 1
    public function describeOrigin(): string
116
    {
117
        return $this->origin;
118 2
    }
119
120
    public function collectDBALColumns(): array
121 2
    {
122
        /** @var array<Column> $additionalColumns */
123 2
        $additionalColumns = array();
124 1
125
        if ($this->column instanceof Column) {
126
            $additionalColumns[] = $this->column;
127 2
        }
128
129
        foreach ($this->fieldMappings as $fieldMapping) {
130 2
            /** @var MappingInterface $fieldMapping */
131
132 2
            $additionalColumns = array_merge(
133
                $additionalColumns,
134
                $fieldMapping->collectDBALColumns()
135
            );
136 2
        }
137
138
        return $additionalColumns;
139 1
    }
140
141 1
    public function getFactory(): ?CallDefinitionInterface
142
    {
143
        return $this->factory;
144 1
    }
145
146 1
    public function getSerializer(): ?CallDefinitionInterface
147
    {
148
        return $this->serializer;
149 1
    }
150
151 1
    public function getId(): ?string
152
    {
153
        return $this->id;
154 1
    }
155
156 1
    public function getReferencedId(): ?string
157
    {
158
        return $this->referencedId;
159 3
    }
160
161
    public function resolveValue(
162
        HydrationContextInterface $context,
163
        array $dataFromAdditionalColumns
164 3
    ) {
165
        /** @var object|null $object */
166
        $object = null;
167 3
168
        /** @var string $columnName */
169 3
        $columnName = $this->column?->getName() ?? '';
170
171 3
        # During creation of an object, the class-name is on the top of that creation stack
172
        $context->pushOnObjectHydrationStack($this->className);
173
174 3
        $reflectionClass = new ReflectionClass($this->className);
175
176 2
        if (!empty($this->referencedId)) {
177
            $object = $context->getRegisteredValue($this->referencedId);
178 2
179
        } elseif ($this->factory instanceof CallDefinitionInterface) {
180 1
            /** @var array<string, string> $factoryData */
181
            $factoryData = $dataFromAdditionalColumns;
182
183 1
            if ($this->column instanceof Column && !array_key_exists("", $factoryData)) {
184
                /** @var Type $type */
185 1
                $type = $this->column->getType();
186
187
                if (array_key_exists($columnName, $dataFromAdditionalColumns)) {
188
                    /** @var Connection $connection */
189
                    $connection = $context->getEntityManager()->getConnection();
190
191
                    if (!is_null($dataFromAdditionalColumns[$columnName])) {
192
                        $dataFromAdditionalColumns[$columnName] = $type->convertToPHPValue(
193
                            $dataFromAdditionalColumns[$columnName],
194
                            $connection->getDatabasePlatform()
195
                        );
196
                    }
197
198
                    $factoryData[""] = $dataFromAdditionalColumns[$columnName];
199
                }
200 2
            }
201
202
            $object = $this->factory->execute(
203
                $context,
204
                $factoryData
205
            );
206 1
            
207 1
        } elseif (is_a($this->className, BackedEnum::class, true)) {
208
            $object = call_user_func($this->className . '::from', $dataFromAdditionalColumns[$columnName]);
209
#            $object = {$this->className}::from($dataFromAdditionalColumns[$columnName]);
210
211 3
        } elseif (is_a($this->className, UnitEnum::class, true)) {
212
            $object = constant(sprintf('%s::%s', $this->className, $dataFromAdditionalColumns[$columnName]));
213 3
214 3
        } else {
215
            if ($reflectionClass->isInstantiable()) {
216 3
                $object = $reflectionClass->newInstanceWithoutConstructor();
217 2
            }
218
        }
219
220 3
        if (is_object($object)) {
221
            // Replace the class-name with the created object on top of the hydration stack
222
            $context->popFromObjectHydrationStack();
223
            $context->pushOnObjectHydrationStack($object);
224 3
225
            if (!empty($this->id) && !$context->hasRegisteredValue($this->id)) {
226
                $context->registerValue($this->id, $object);
227
            }
228
229
            foreach ($this->fieldMappings as $fieldName => $fieldMapping) {
230 3
                /** @var MappingInterface $fieldMapping */
231
232 3
                /** @var mixed $fieldValue */
233
                $fieldValue = $fieldMapping->resolveValue(
234
                    $context,
235
                    $dataFromAdditionalColumns
236 3
                );
237
238
                /** @var ReflectionClass $propertyReflectionClass */
239
                $propertyReflectionClass = $reflectionClass;
240
241
                while (is_object($propertyReflectionClass) && !$propertyReflectionClass->hasProperty($fieldName)) {
242
                    $propertyReflectionClass = $propertyReflectionClass->getParentClass();
243
                }
244
245
                if (!is_object($propertyReflectionClass)) {
246 3
                    throw new ReflectionException(sprintf(
247
                        "Property '%s' does not exist on class '%s' as defined at '%s'",
248 3
                        $fieldName,
249 3
                        $reflectionClass->getName(),
250
                        $reflectionClass->getFileName()
251
                    ));
252
                }
253 3
254
                /** @var ReflectionProperty $reflectionProperty */
255 3
                $reflectionProperty = $propertyReflectionClass->getProperty($fieldName);
256
257
                $reflectionProperty->setAccessible(true);
258 2
                $reflectionProperty->setValue($object, $fieldValue);
259
            }
260
        }
261
262
        $context->popFromObjectHydrationStack();
263 2
264
        return $object;
265 2
    }
266
267 2
    public function revertValue(
268
        HydrationContextInterface $context,
269 2
        $valueFromEntityField
270
    ): array {
271 2
        /** @var array<scalar> $data */
272
        $data = array();
273
274
        $this->assertValue($context, [], $valueFromEntityField);
275 2
276
        $reflectionClass = new ReflectionClass($this->className);
277 2
278
        $context->pushOnObjectHydrationStack($valueFromEntityField);
279
280
        foreach ($this->fieldMappings as $fieldName => $fieldMapping) {
281 2
            /** @var MappingInterface $fieldMapping */
282
283
            /** @var ReflectionClass $propertyReflectionClass */
284
            $propertyReflectionClass = $reflectionClass;
285
286
            while (is_object($propertyReflectionClass) && !$propertyReflectionClass->hasProperty($fieldName)) {
287
                $propertyReflectionClass = $propertyReflectionClass->getParentClass();
288
            }
289
290
            if (!is_object($propertyReflectionClass)) {
291 2
                throw new ReflectionException(sprintf(
292 2
                    "Property %s does not exist in class %s. (Defined %s)",
293
                    $fieldName,
294
                    $this->className,
295 2
                    $this->origin
296
                ));
297 2
            }
298 1
299
            /** @var ReflectionProperty $reflectionProperty */
300
            $reflectionProperty = $propertyReflectionClass->getProperty($fieldName);
301
            $reflectionProperty->setAccessible(true);
302 2
303
            /** @var mixed $valueFromField */
304
            $valueFromField = null;
305
306
            if (is_object($valueFromEntityField) && $reflectionProperty->isInitialized($valueFromEntityField)) {
307 2
                $valueFromField = $reflectionProperty->getValue($valueFromEntityField);
308
            }
309
310
            /** @var array<string, mixed> $fieldData */
311
            $fieldData = $fieldMapping->revertValue(
312 2
                $context,
313
                $valueFromField
314
            );
315 2
316
            if (array_key_exists("", $fieldData)) {
317
                $fieldData[$fieldName] = $fieldData[""];
318
                unset($fieldData[""]);
319
            }
320
321
            $data = array_merge($data, $fieldData);
322
        }
323
324
        /** @var string $columnName */
325
        $columnName = '';
326
327
        if ($this->column instanceof Column) {
328
            $columnName = $this->column->getName();
329
        }
330
331
        if ($this->serializer instanceof CallDefinitionInterface) {
332
            $data[$columnName] = $this->serializer->execute(
333
                $context,
334
                array_merge($data, ['' => $valueFromEntityField])
335
            );
336
337
            if ($this->column instanceof Column) {
338
                /** @var Type $type */
339
                $type = $this->column->getType();
340
341
                /** @var Connection $connection */
342 2
                $connection = $context->getEntityManager()->getConnection();
343
344 2
                $data[$columnName] = $type->convertToDatabaseValue(
345
                    $data[$columnName],
346
                    $connection->getDatabasePlatform()
347 3
                );
348
            }
349
            
350
        } elseif (is_a($this->className, BackedEnum::class, true)) {
351
            $data[$columnName] = $valueFromEntityField->value;
352 3
353 1
        } elseif (is_a($this->className, UnitEnum::class, true)) {
354 1
            $data[$columnName] = $valueFromEntityField->name;
355 1
        }
356 1
357
        $context->popFromObjectHydrationStack();
358
359
        return $data;
360
    }
361 1
362
    public function assertValue(
363 1
        HydrationContextInterface $context,
364 1
        array $dataFromAdditionalColumns,
365
        $actualValue
366
    ): void {
367 1
        if (!is_null($actualValue) && !$actualValue instanceof $this->className) {
368 1
            throw FailedRDMAssertionException::expectedInstanceOf(
369
                $this->className,
370
                is_object($actualValue) ?get_class($actualValue) :gettype($actualValue),
371
                $this->origin
372
            );
373
        }
374
    }
375
376
    public function wakeUpMapping(ContainerInterface $container): void
377
    {
378
        if ($this->factory instanceof CallDefinitionInterface) {
379
            $this->factory->wakeUpCall($container);
380
        }
381
382
        if ($this->serializer instanceof CallDefinitionInterface) {
383
            $this->serializer->wakeUpCall($container);
384
        }
385
        
386
        foreach ($this->fieldMappings as $fieldName => $fieldMapping) {
387
            /** @var MappingInterface $fieldMapping */
388
389
            $fieldMapping->wakeUpMapping($container);
390
        }
391
    }
392
393
}
394