ObjectMapping::getDBALColumn()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

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