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

ObjectMapping::getFieldMappings()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 1
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