Passed
Push — master ( 50749f...7b5f02 )
by Gerrit
12:36
created

ObjectMapping::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 25
ccs 13
cts 13
cp 1
rs 9.52
c 0
b 0
f 0
cc 2
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 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
28
final class ObjectMapping implements MappingInterface
29
{
30
31
    /**
32
     * @var class-string
33
     */
34
    private $className;
35
36
    /**
37
     * @var array<MappingInterface>
38
     */
39
    private $fieldMappings = array();
40
41
    /**
42
     * @var Column|null
43
     */
44
    private $column;
45
46
    /**
47
     * @var CallDefinitionInterface|null
48
     */
49
    private $factory;
50
51
    /**
52
     * @var CallDefinitionInterface|null
53
     */
54
    private $serializer;
55
56
    /**
57
     * @var string
58
     */
59
    private $origin;
60
61
    /**
62
     * @var string|null
63
     */
64
    private $id;
65
66
    /**
67
     * @var string|null
68
     */
69
    private $referencedId;
70
71
    /** @param class-string $className */
0 ignored issues
show
Documentation introduced by
The doc-type class-string could not be parsed: Unknown type name "class-string" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
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 17
            Assert::isInstanceOf($fieldMapping, MappingInterface::class);
92 17
            Assert::false(is_numeric($fieldName));
93
94 17
            $this->fieldMappings[$fieldName] = $fieldMapping;
95
        }
96 17
    }
97
98 1
    public function getClassName(): string
99
    {
100 1
        return $this->className;
101
    }
102
103 1
    public function getDBALColumn(): ?Column
104
    {
105 1
        return $this->column;
106
    }
107
108 1
    public function getFieldMappings(): array
109
    {
110 1
        return $this->fieldMappings;
111
    }
112
113 1
    public function describeOrigin(): string
114
    {
115 1
        return $this->origin;
116
    }
117
118 2
    public function collectDBALColumns(): array
119
    {
120
        /** @var array<Column> $additionalColumns */
121 2
        $additionalColumns = array();
122
123 2
        if ($this->column instanceof Column) {
124 1
            $additionalColumns[] = $this->column;
125
        }
126
127 2
        foreach ($this->fieldMappings as $fieldMapping) {
128
            /** @var MappingInterface $fieldMapping */
129
130 2
            $additionalColumns = array_merge(
131 2
                $additionalColumns,
132 2
                $fieldMapping->collectDBALColumns()
133
            );
134
        }
135
136 2
        return $additionalColumns;
137
    }
138
139 1
    public function getFactory(): ?CallDefinitionInterface
140
    {
141 1
        return $this->factory;
142
    }
143
144 1
    public function getSerializer(): ?CallDefinitionInterface
145
    {
146 1
        return $this->serializer;
147
    }
148
149 1
    public function getId(): ?string
150
    {
151 1
        return $this->id;
152
    }
153
154 1
    public function getReferencedId(): ?string
155
    {
156 1
        return $this->referencedId;
157
    }
158
159 3
    public function resolveValue(
160
        HydrationContextInterface $context,
161
        array $dataFromAdditionalColumns
162
    ) {
163
        /** @var object|null $object */
164 3
        $object = null;
165
166
        # During creation of an object, the class-name is on the top of that creation stack
167 3
        $context->pushOnObjectHydrationStack($this->className);
168
169 3
        $reflectionClass = new ReflectionClass($this->className);
170
171 3
        if (!empty($this->referencedId)) {
172
            $object = $context->getRegisteredValue($this->referencedId);
173
174 3
        } elseif ($this->factory instanceof CallDefinitionInterface) {
175
            /** @var array<string, string> $factoryData */
176 2
            $factoryData = $dataFromAdditionalColumns;
177
178 2
            if ($this->column instanceof Column && !array_key_exists("", $factoryData)) {
179
                /** @var Type $type */
180 1
                $type = $this->column->getType();
181
182
                /** @var string $columnName */
183 1
                $columnName = $this->column->getName();
184
185 1
                if (isset($dataFromAdditionalColumns[$columnName])) {
186
                    /** @var Connection $connection */
187
                    $connection = $context->getEntityManager()->getConnection();
188
189
                    $dataFromAdditionalColumns[$columnName] = $type->convertToPHPValue(
190
                        $dataFromAdditionalColumns[$columnName],
191
                        $connection->getDatabasePlatform()
192
                    );
193
194
                    $factoryData[""] = $dataFromAdditionalColumns[$columnName];
195
                }
196
            }
197
198 2
            $object = $this->factory->execute(
199 2
                $context,
200
                $factoryData
201
            );
202
203
        } else {
204 1
            if ($reflectionClass->isInstantiable()) {
205 1
                $object = $reflectionClass->newInstanceWithoutConstructor();
206
            }
207
        }
208
209 3
        Assert::object($object);
210
211
        // Replace the class-name with the created object on top of the hydration stack
212 3
        $context->popFromObjectHydrationStack();
213 3
        $context->pushOnObjectHydrationStack($object);
214
215 3
        if (!empty($this->id) && !$context->hasRegisteredValue($this->id)) {
216 2
            $context->registerValue($this->id, $object);
217
        }
218
219 3
        foreach ($this->fieldMappings as $fieldName => $fieldMapping) {
220
            /** @var MappingInterface $fieldMapping */
221
222
            /** @var mixed $fieldValue */
223 3
            $fieldValue = $fieldMapping->resolveValue(
224 3
                $context,
225
                $dataFromAdditionalColumns
226
            );
227
228
            /** @var ReflectionClass $propertyReflectionClass */
229 3
            $propertyReflectionClass = $reflectionClass;
230
231 3
            while (is_object($propertyReflectionClass) && !$propertyReflectionClass->hasProperty($fieldName)) {
232
                $propertyReflectionClass = $propertyReflectionClass->getParentClass();
233
            }
234
235 3
            if (!is_object($propertyReflectionClass)) {
236
                throw new ReflectionException(sprintf(
237
                    "Property '%s' does not exist on class '%s' as defined at '%s'",
238
                    $fieldName,
239
                    $reflectionClass->getName(),
0 ignored issues
show
Bug introduced by
Consider using $reflectionClass->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
240
                    $reflectionClass->getFileName()
241
                ));
242
            }
243
244
            /** @var ReflectionProperty $reflectionProperty */
245 3
            $reflectionProperty = $propertyReflectionClass->getProperty($fieldName);
246
247 3
            $reflectionProperty->setAccessible(true);
248 3
            $reflectionProperty->setValue($object, $fieldValue);
249
        }
250
251 3
        $context->popFromObjectHydrationStack();
252
253 3
        return $object;
254
    }
255
256 2
    public function revertValue(
257
        HydrationContextInterface $context,
258
        $valueFromEntityField
259
    ): array {
260
        /** @var array<scalar> $data */
261 2
        $data = array();
262
263 2
        $this->assertValue($context, [], $valueFromEntityField);
264
265 2
        $reflectionClass = new ReflectionClass($this->className);
266
267 2
        $context->pushOnObjectHydrationStack($valueFromEntityField);
268
269 2
        foreach ($this->fieldMappings as $fieldName => $fieldMapping) {
270
            /** @var MappingInterface $fieldMapping */
271
272
            /** @var ReflectionClass $propertyReflectionClass */
273 2
            $propertyReflectionClass = $reflectionClass;
274
275 2
            while (is_object($propertyReflectionClass) && !$propertyReflectionClass->hasProperty($fieldName)) {
276
                $propertyReflectionClass = $propertyReflectionClass->getParentClass();
277
            }
278
279 2
            if (!is_object($propertyReflectionClass)) {
280
                throw new ReflectionException(sprintf(
281
                    "Property %s does not exist in class %s. (Defined %s)",
282
                    $fieldName,
283
                    $this->className,
284
                    $this->origin
285
                ));
286
            }
287
288
            /** @var ReflectionProperty $reflectionProperty */
289 2
            $reflectionProperty = $propertyReflectionClass->getProperty($fieldName);
290
291 2
            $reflectionProperty->setAccessible(true);
292 2
            $valueFromField = null;
293
294 2
            if (is_object($valueFromEntityField)) {
295 1
                $valueFromField = $reflectionProperty->getValue($valueFromEntityField);
296
            }
297
298
            /** @var array<string, mixed> $fieldData */
299 2
            $fieldData = $fieldMapping->revertValue(
300 2
                $context,
301
                $valueFromField
302
            );
303
304 2
            if (array_key_exists("", $fieldData)) {
305
                $fieldData[$fieldName] = $fieldData[""];
306
                unset($fieldData[""]);
307
            }
308
309 2
            $data = array_merge($data, $fieldData);
310
        }
311
312 2
        if ($this->serializer instanceof CallDefinitionInterface) {
313
            /** @var string $columnName */
314
            $columnName = '';
315
316
            if ($this->column instanceof Column) {
317
                $columnName = $this->column->getName();
318
            }
319
320
            $data[$columnName] = $this->serializer->execute(
321
                $context,
322
                array_merge($data, ['' => $valueFromEntityField])
323
            );
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
                is_object($actualValue) ?get_class($actualValue) :gettype($actualValue),
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