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

ObjectMapping::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 23
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 2
eloc 11
c 1
b 0
f 1
nc 2
nop 8
dl 0
loc 23
ccs 11
cts 11
cp 1
crap 2
rs 9.9

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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