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

ObjectMapping::getId()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

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