ObjectMapping::resolveValue()   C
last analyzed

Complexity

Conditions 16
Paths 63

Size

Total Lines 106
Code Lines 50

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 31
CRAP Score 20.5988

Importance

Changes 4
Bugs 4 Features 0
Metric Value
cc 16
eloc 50
c 4
b 4
f 0
nc 63
nop 2
dl 0
loc 106
ccs 31
cts 42
cp 0.7381
crap 20.5988
rs 5.5666

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 
184
            # && !array_key_exists("", $factoryData)
185 1
            ) {
186
                /** @var Type $type */
187
                $type = $this->column->getType();
188
189
                if (array_key_exists($columnName, $dataFromAdditionalColumns)) {
190
                    /** @var Connection $connection */
191
                    $connection = $context->getEntityManager()->getConnection();
192
193
                    if (!is_null($dataFromAdditionalColumns[$columnName])) {
194
                        $dataFromAdditionalColumns[$columnName] = $type->convertToPHPValue(
195
                            $dataFromAdditionalColumns[$columnName],
196
                            $connection->getDatabasePlatform()
197
                        );
198
                    }
199
200 2
                    $factoryData[""] = $dataFromAdditionalColumns[$columnName];
201
                }
202
            }
203
204
            $object = $this->factory->execute(
205
                $context,
206 1
                $factoryData
207 1
            );
208
            
209
        } elseif (is_a($this->className, BackedEnum::class, true)) {
210
            $object = call_user_func($this->className . '::from', $dataFromAdditionalColumns[$columnName]);
211 3
#            $object = {$this->className}::from($dataFromAdditionalColumns[$columnName]);
212
213 3
        } elseif (is_a($this->className, UnitEnum::class, true)) {
214 3
            $object = constant(sprintf('%s::%s', $this->className, $dataFromAdditionalColumns[$columnName]));
215
216 3
        } else {
217 2
            if ($reflectionClass->isInstantiable()) {
218
                $object = $reflectionClass->newInstanceWithoutConstructor();
219
            }
220 3
        }
221
222
        if (is_object($object)) {
223
            // Replace the class-name with the created object on top of the hydration stack
224 3
            $context->popFromObjectHydrationStack();
225
            $context->pushOnObjectHydrationStack($object);
226
227
            if (!empty($this->id) && !$context->hasRegisteredValue($this->id)) {
228
                $context->registerValue($this->id, $object);
229
            }
230 3
231
            foreach ($this->fieldMappings as $fieldName => $fieldMapping) {
232 3
                /** @var MappingInterface $fieldMapping */
233
234
                /** @var mixed $fieldValue */
235
                $fieldValue = $fieldMapping->resolveValue(
236 3
                    $context,
237
                    $dataFromAdditionalColumns[''] ?? $dataFromAdditionalColumns
238
                );
239
240
                /** @var ReflectionClass $propertyReflectionClass */
241
                $propertyReflectionClass = $reflectionClass;
242
243
                while (is_object($propertyReflectionClass) && !$propertyReflectionClass->hasProperty($fieldName)) {
244
                    $propertyReflectionClass = $propertyReflectionClass->getParentClass();
245
                }
246 3
247
                if (!is_object($propertyReflectionClass)) {
248 3
                    throw new ReflectionException(sprintf(
249 3
                        "Property '%s' does not exist on class '%s' as defined at '%s'",
250
                        $fieldName,
251
                        $reflectionClass->getName(),
252
                        $reflectionClass->getFileName()
253 3
                    ));
254
                }
255 3
256
                /** @var ReflectionProperty $reflectionProperty */
257
                $reflectionProperty = $propertyReflectionClass->getProperty($fieldName);
258 2
259
                $reflectionProperty->setAccessible(true);
260
                $reflectionProperty->setValue($object, $fieldValue);
261
            }
262
        }
263 2
264
        $context->popFromObjectHydrationStack();
265 2
266
        return $object;
267 2
    }
268
269 2
    public function revertValue(
270
        HydrationContextInterface $context,
271 2
        $valueFromEntityField
272
    ): array {
273
        /** @var array<scalar> $data */
274
        $data = array();
275 2
276
        $this->assertValue($context, [], $valueFromEntityField);
277 2
278
        $reflectionClass = new ReflectionClass($this->className);
279
280
        $context->pushOnObjectHydrationStack($valueFromEntityField);
281 2
282
        foreach ($this->fieldMappings as $fieldName => $fieldMapping) {
283
            /** @var MappingInterface $fieldMapping */
284
285
            /** @var ReflectionClass $propertyReflectionClass */
286
            $propertyReflectionClass = $reflectionClass;
287
288
            while (is_object($propertyReflectionClass) && !$propertyReflectionClass->hasProperty($fieldName)) {
289
                $propertyReflectionClass = $propertyReflectionClass->getParentClass();
290
            }
291 2
292 2
            if (!is_object($propertyReflectionClass)) {
293
                throw new ReflectionException(sprintf(
294
                    "Property %s does not exist in class %s. (Defined %s)",
295 2
                    $fieldName,
296
                    $this->className,
297 2
                    $this->origin
298 1
                ));
299
            }
300
301
            /** @var ReflectionProperty $reflectionProperty */
302 2
            $reflectionProperty = $propertyReflectionClass->getProperty($fieldName);
303
            $reflectionProperty->setAccessible(true);
304
305
            /** @var mixed $valueFromField */
306
            $valueFromField = null;
307 2
308
            if (is_object($valueFromEntityField) && $reflectionProperty->isInitialized($valueFromEntityField)) {
309
                $valueFromField = $reflectionProperty->getValue($valueFromEntityField);
310
            }
311
312 2
            /** @var array<string, mixed> $fieldData */
313
            $fieldData = $fieldMapping->revertValue(
314
                $context,
315 2
                $valueFromField
316
            );
317
318
            if (array_key_exists("", $fieldData)) {
319
                $fieldData[$fieldName] = $fieldData[""];
320
                unset($fieldData[""]);
321
            }
322
323
            $data = array_merge($data, $fieldData);
324
        }
325
326
        /** @var string $columnName */
327
        $columnName = '';
328
329
        if ($this->column instanceof Column) {
330
            $columnName = $this->column->getName();
331
        }
332
333
        if ($this->serializer instanceof CallDefinitionInterface) {
334
            $data[$columnName] = $this->serializer->execute(
335
                $context,
336
                array_merge($data, ['' => $valueFromEntityField])
337
            );
338
339
            if ($this->column instanceof Column) {
340
                /** @var Type $type */
341
                $type = $this->column->getType();
342 2
343
                /** @var Connection $connection */
344 2
                $connection = $context->getEntityManager()->getConnection();
345
346
                $data[$columnName] = $type->convertToDatabaseValue(
347 3
                    $data[$columnName],
348
                    $connection->getDatabasePlatform()
349
                );
350
            }
351
            
352 3
        } elseif (is_a($this->className, BackedEnum::class, true)) {
353 1
            $data[$columnName] = $valueFromEntityField->value;
354 1
355 1
        } elseif (is_a($this->className, UnitEnum::class, true)) {
356 1
            $data[$columnName] = $valueFromEntityField->name;
357
        }
358
359
        $context->popFromObjectHydrationStack();
360
361 1
        return $data;
362
    }
363 1
364 1
    public function assertValue(
365
        HydrationContextInterface $context,
366
        array $dataFromAdditionalColumns,
367 1
        $actualValue
368 1
    ): void {
369
        if (!is_null($actualValue) && !$actualValue instanceof $this->className) {
370
            throw FailedRDMAssertionException::expectedInstanceOf(
371
                $this->className,
372
                is_object($actualValue) ?get_class($actualValue) :gettype($actualValue),
373
                $this->origin
374
            );
375
        }
376
    }
377
378
    public function wakeUpMapping(ContainerInterface $container): void
379
    {
380
        if ($this->factory instanceof CallDefinitionInterface) {
381
            $this->factory->wakeUpCall($container);
382
        }
383
384
        if ($this->serializer instanceof CallDefinitionInterface) {
385
            $this->serializer->wakeUpCall($container);
386
        }
387
        
388
        foreach ($this->fieldMappings as $fieldName => $fieldMapping) {
389
            /** @var MappingInterface $fieldMapping */
390
391
            $fieldMapping->wakeUpMapping($container);
392
        }
393
    }
394
395
}
396