Passed
Push — master ( 8b21d0...6a3b4a )
by Gerrit
03:23
created

ObjectMapping::__construct()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 27
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 27
ccs 13
cts 13
cp 1
rs 8.8571
c 0
b 0
f 0
cc 2
eloc 20
nc 2
nop 8
crap 2

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 Addiks\RDMBundle\Mapping\ObjectMappingInterface;
16
use Doctrine\DBAL\Schema\Column;
17
use Addiks\RDMBundle\Mapping\MappingInterface;
18
use Webmozart\Assert\Assert;
19
use Addiks\RDMBundle\Hydration\HydrationContextInterface;
20
use Addiks\RDMBundle\Mapping\CallDefinitionInterface;
21
use Addiks\RDMBundle\Exception\FailedRDMAssertionException;
22
use ReflectionClass;
23
use ReflectionProperty;
24
use ReflectionException;
25
use Symfony\Component\DependencyInjection\ContainerInterface;
26
use Doctrine\DBAL\Types\Type;
27
use Doctrine\DBAL\Connection;
28
29
final class ObjectMapping implements ObjectMappingInterface
30
{
31
32
    /**
33
     * @var string
34
     */
35
    private $className;
36
37
    /**
38
     * @var array<MappingInterface>
39
     */
40
    private $fieldMappings = array();
41
42
    /**
43
     * @var Column|null
44
     */
45
    private $column;
46
47
    /**
48
     * @var CallDefinitionInterface|null
49
     */
50
    private $factory;
51
52
    /**
53
     * @var CallDefinitionInterface|null
54
     */
55
    private $serializer;
56
57
    /**
58
     * @var string
59
     */
60
    private $origin;
61
62
    /**
63
     * @var string|null
64
     */
65
    private $id;
66
67
    /**
68
     * @var string|null
69
     */
70
    private $referencedId;
71
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
            /** @var MappingInterface $fieldMapping */
92
93 17
            Assert::isInstanceOf($fieldMapping, MappingInterface::class);
94 17
            Assert::false(is_numeric($fieldName));
95
96 17
            $this->fieldMappings[$fieldName] = $fieldMapping;
97
        }
98 17
    }
99
100 1
    public function getClassName(): string
101
    {
102 1
        return $this->className;
103
    }
104
105 1
    public function getDBALColumn(): ?Column
106
    {
107 1
        return $this->column;
108
    }
109
110 1
    public function getFieldMappings(): array
111
    {
112 1
        return $this->fieldMappings;
113
    }
114
115 1
    public function describeOrigin(): string
116
    {
117 1
        return $this->origin;
118
    }
119
120 2
    public function collectDBALColumns(): array
121
    {
122
        /** @var array<Column> $additionalColumns */
123 2
        $additionalColumns = array();
124
125 2
        if ($this->column instanceof Column) {
126 1
            $additionalColumns[] = $this->column;
127
        }
128
129 2
        foreach ($this->fieldMappings as $fieldMapping) {
130
            /** @var MappingInterface $fieldMapping */
131
132 2
            $additionalColumns = array_merge(
133 2
                $additionalColumns,
134 2
                $fieldMapping->collectDBALColumns()
135
            );
136
        }
137
138 2
        return $additionalColumns;
139
    }
140
141 1
    public function getFactory(): ?CallDefinitionInterface
142
    {
143 1
        return $this->factory;
144
    }
145
146 1
    public function getSerializer(): ?CallDefinitionInterface
147
    {
148 1
        return $this->serializer;
149
    }
150
151 1
    public function getId(): ?string
152
    {
153 1
        return $this->id;
154
    }
155
156 1
    public function getReferencedId(): ?string
157
    {
158 1
        return $this->referencedId;
159
    }
160
161 3
    public function resolveValue(
162
        HydrationContextInterface $context,
163
        array $dataFromAdditionalColumns
164
    ) {
165
        /** @var mixed $object */
166 3
        $object = null;
0 ignored issues
show
Unused Code introduced by
$object is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
167
168 3
        $reflectionClass = new ReflectionClass($this->className);
169
170
        /** @var object|string $object */
171 3
        $object = $this->className;
172
173 3
        if ($reflectionClass->isInstantiable()) {
174 3
            $object = $reflectionClass->newInstanceWithoutConstructor();
175
        }
176
177 3
        $context->pushOnObjectHydrationStack($object);
178
179 3
        if (!empty($this->referencedId)) {
180
            $object = $context->getRegisteredValue($this->referencedId);
181
182 3
        } elseif ($this->factory instanceof CallDefinitionInterface) {
183
            /** @var array<string, string> $factoryData */
184 2
            $factoryData = $dataFromAdditionalColumns;
185
186 2
            if ($this->column instanceof Column && !array_key_exists("", $factoryData)) {
187
                /** @var Type $type */
188 1
                $type = $this->column->getType();
189
190
                /** @var string $columnName */
191 1
                $columnName = $this->column->getName();
192
193 1
                if (isset($dataFromAdditionalColumns[$columnName])) {
194
                    /** @var Connection $connection */
195
                    $connection = $context->getEntityManager()->getConnection();
196
197
                    $dataFromAdditionalColumns[$columnName] = $type->convertToPHPValue(
198
                        $dataFromAdditionalColumns[$columnName],
199
                        $connection->getDatabasePlatform()
200
                    );
201
202
                    $factoryData[""] = $dataFromAdditionalColumns[$columnName];
203
                }
204
            }
205
206 2
            $object = $this->factory->execute(
207 2
                $context,
208 2
                $factoryData
209
            );
210
        }
211
212
        // $object may have been replaced during creation, re-assign on top of 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 3
                $context,
226 3
                $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("Property %s does not exist", $fieldName));
238
            }
239
240
            /** @var ReflectionProperty $reflectionProperty */
241 3
            $reflectionProperty = $propertyReflectionClass->getProperty($fieldName);
242
243 3
            $reflectionProperty->setAccessible(true);
244 3
            $reflectionProperty->setValue($object, $fieldValue);
245
        }
246
247 3
        $context->popFromObjectHydrationStack();
248
249 3
        return $object;
250
    }
251
252 2
    public function revertValue(
253
        HydrationContextInterface $context,
254
        $valueFromEntityField
255
    ): array {
256
        /** @var array<scalar> $data */
257 2
        $data = array();
258
259 2
        $this->assertValue($context, [], $valueFromEntityField);
260
261 2
        $reflectionClass = new ReflectionClass($this->className);
262
263 2
        $context->pushOnObjectHydrationStack($valueFromEntityField);
264
265 2
        foreach ($this->fieldMappings as $fieldName => $fieldMapping) {
266
            /** @var MappingInterface $fieldMapping */
267
268
            /** @var ReflectionClass $propertyReflectionClass */
269 2
            $propertyReflectionClass = $reflectionClass;
270
271 2
            while (is_object($propertyReflectionClass) && !$propertyReflectionClass->hasProperty($fieldName)) {
272
                $propertyReflectionClass = $propertyReflectionClass->getParentClass();
273
            }
274
275 2
            if (!is_object($propertyReflectionClass)) {
276
                throw new ReflectionException(sprintf(
277
                    "Property %s does not exist in class %s. (Defined %s)",
278
                    $fieldName,
279
                    get_class($valueFromEntityField),
280
                    $this->origin
281
                ));
282
            }
283
284
            /** @var ReflectionProperty $reflectionProperty */
285 2
            $reflectionProperty = $propertyReflectionClass->getProperty($fieldName);
286
287 2
            $reflectionProperty->setAccessible(true);
288 2
            $valueFromField = null;
289
290 2
            if (is_object($valueFromEntityField)) {
291 1
                $valueFromField = $reflectionProperty->getValue($valueFromEntityField);
292
            }
293
294
            /** @var array<string, mixed> $fieldData */
295 2
            $fieldData = $fieldMapping->revertValue(
296 2
                $context,
297 2
                $valueFromField
298
            );
299
300 2
            if (array_key_exists("", $fieldData)) {
301
                $fieldData[$fieldName] = $fieldData[""];
302
                unset($fieldData[""]);
303
            }
304
305 2
            $data = array_merge($data, $fieldData);
306
        }
307
308 2
        if ($this->serializer instanceof CallDefinitionInterface) {
309
            /** @var string $columnName */
310
            $columnName = '';
311
312
            if ($this->column instanceof Column) {
313
                $columnName = $this->column->getName();
314
            }
315
316
            $data[''] = $valueFromEntityField;
317
318
            $data[$columnName] = $this->serializer->execute(
319
                $context,
320
                $data
321
            );
322
323
            unset($data['']);
324
325
            if ($this->column instanceof Column) {
326
                /** @var Type $type */
327
                $type = $this->column->getType();
328
329
                /** @var Connection $connection */
330
                $connection = $context->getEntityManager()->getConnection();
331
332
                $data[$columnName] = $type->convertToDatabaseValue(
333
                    $data[$columnName],
334
                    $connection->getDatabasePlatform()
335
                );
336
            }
337
        }
338
339 2
        $context->popFromObjectHydrationStack();
340
341 2
        return $data;
342
    }
343
344 3
    public function assertValue(
345
        HydrationContextInterface $context,
346
        array $dataFromAdditionalColumns,
347
        $actualValue
348
    ): void {
349 3
        if (!is_null($actualValue) && !$actualValue instanceof $this->className) {
350 1
            throw FailedRDMAssertionException::expectedInstanceOf(
351 1
                $this->className,
352 1
                $context->getEntityClass(),
353 1
                $this->origin
354
            );
355
        }
356 2
    }
357
358 1
    public function wakeUpMapping(ContainerInterface $container): void
359
    {
360 1
        if ($this->factory instanceof CallDefinitionInterface) {
361 1
            $this->factory->wakeUpCall($container);
362
        }
363
364 1
        if ($this->serializer instanceof CallDefinitionInterface) {
365 1
            $this->serializer->wakeUpCall($container);
366
        }
367 1
    }
368
369
}
370