ObjectMapping::__construct()   A
last analyzed

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 
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