Completed
Push — master ( 77cc29...840912 )
by Gerrit
10:19
created

MappingXmlDriver::readCallDefinition()   B

Complexity

Conditions 3
Paths 4

Size

Total Lines 28
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 3.0026

Importance

Changes 0
Metric Value
dl 0
loc 28
ccs 14
cts 15
cp 0.9333
rs 8.8571
c 0
b 0
f 0
cc 3
eloc 14
nc 4
nop 1
crap 3.0026
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
 * @license GPL-3.0
8
 * @author Gerrit Addiks <[email protected]>
9
 */
10
11
namespace Addiks\RDMBundle\Mapping\Drivers;
12
13
use DOMDocument;
14
use DOMXPath;
15
use DOMNode;
16
use Addiks\RDMBundle\Mapping\Drivers\MappingDriverInterface;
17
use Doctrine\Common\Persistence\Mapping\Driver\FileLocator;
18
use Addiks\RDMBundle\Mapping\EntityMappingInterface;
19
use Addiks\RDMBundle\Mapping\EntityMapping;
20
use Addiks\RDMBundle\Mapping\ServiceMapping;
21
use Addiks\RDMBundle\Mapping\MappingInterface;
22
use Addiks\RDMBundle\Mapping\ChoiceMapping;
23
use DOMAttr;
24
use Doctrine\DBAL\Schema\Column;
25
use Doctrine\DBAL\Types\Type;
26
use Addiks\RDMBundle\Mapping\ObjectMapping;
27
use Addiks\RDMBundle\Mapping\ObjectMappingInterface;
28
use Addiks\RDMBundle\Mapping\ChoiceMappingInterface;
29
use Addiks\RDMBundle\Mapping\ServiceMappingInterface;
30
use DOMNamedNodeMap;
31
use Addiks\RDMBundle\Mapping\CallDefinitionInterface;
32
use Addiks\RDMBundle\Mapping\CallDefinition;
33
use Addiks\RDMBundle\Mapping\FieldMapping;
34
use Addiks\RDMBundle\Mapping\ArrayMapping;
35
use Addiks\RDMBundle\Mapping\ArrayMappingInterface;
36
use Addiks\RDMBundle\Mapping\ListMapping;
37
use Addiks\RDMBundle\Mapping\ListMappingInterface;
38
use Addiks\RDMBundle\Exception\InvalidMappingException;
39
use Addiks\RDMBundle\Mapping\NullMapping;
40
use Symfony\Component\HttpKernel\KernelInterface;
41
use Addiks\RDMBundle\Mapping\NullableMapping;
42
use Addiks\RDMBundle\Mapping\NullableMappingInterface;
43
use Symfony\Component\DependencyInjection\ContainerInterface;
44
45
final class MappingXmlDriver implements MappingDriverInterface
46
{
47
48
    const RDM_SCHEMA_URI = "http://github.com/addiks/symfony_rdm/tree/master/Resources/mapping-schema.v1.xsd";
49
    const DOCTRINE_SCHEMA_URI = "http://doctrine-project.org/schemas/orm/doctrine-mapping";
50
51
    /**
52
     * @var FileLocator
53
     */
54
    private $doctrineFileLocator;
55
56
    /**
57
     * @var KernelInterface
58
     */
59
    private $kernel;
60
61
    /**
62
     * @var string
63
     */
64
    private $schemaFilePath;
65
66 3
    public function __construct(
67
        FileLocator $doctrineFileLocator,
68
        KernelInterface $kernel,
69
        string $schemaFilePath
70
    ) {
71 3
        $this->doctrineFileLocator = $doctrineFileLocator;
72 3
        $this->kernel = $kernel;
73 3
        $this->schemaFilePath = $schemaFilePath;
74 3
    }
75
76 2
    public function loadRDMMetadataForClass(string $className): ?EntityMappingInterface
77
    {
78
        /** @var ?EntityMappingInterface $mapping */
0 ignored issues
show
Documentation introduced by
The doc-type ?EntityMappingInterface could not be parsed: Unknown type name "?EntityMappingInterface" 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...
79 2
        $mapping = null;
80
81
        /** @var array<MappingInterface> $fieldMappings */
82 2
        $fieldMappings = array();
83
84 2
        if ($this->doctrineFileLocator->fileExists($className)) {
85
            /** @var string $mappingFile */
86 2
            $mappingFile = $this->doctrineFileLocator->findMappingFile($className);
87
88 2
            $fieldMappings = $this->readFieldMappingsFromFile($mappingFile);
89
        }
90
91 1
        if (!empty($fieldMappings)) {
92 1
            $mapping = new EntityMapping($className, $fieldMappings);
93
        }
94
95 1
        return $mapping;
96
    }
97
98
    /**
99
     * @return array<MappingInterface>
100
     */
101 2
    private function readFieldMappingsFromFile(string $mappingFile, string $parentMappingFile = null): array
102
    {
103 2
        if ($mappingFile[0] === '@') {
104
            /** @var string $mappingFile */
105 1
            $mappingFile = $this->kernel->locateResource($mappingFile);
106
        }
107
108 2
        if ($mappingFile[0] !== DIRECTORY_SEPARATOR && !empty($parentMappingFile)) {
109 1
            $mappingFile = dirname($parentMappingFile) . DIRECTORY_SEPARATOR . $mappingFile;
110
        }
111
112 2
        if (!file_exists($mappingFile)) {
113 1
            throw new InvalidMappingException(sprintf(
114 1
                "Missing referenced orm file '%s', referenced in file '%s'!",
115 1
                $mappingFile,
116 1
                $parentMappingFile
117
            ));
118
        }
119
120 2
        $dom = new DOMDocument();
121 2
        $dom->loadXML(file_get_contents($mappingFile));
122
123
        /** @var DOMXPath $xpath */
124 2
        $xpath = $this->createXPath($dom->documentElement);
125
126
        /** @var array<MappingInterface> $fieldMappings */
127 2
        $fieldMappings = $this->readFieldMappings(
128 2
            $dom,
129 2
            $mappingFile,
130 2
            null,
131 2
            false
132
        );
133
134 2
        foreach ($xpath->query("//orm:entity", $dom) as $entityNode) {
135
            /** @var DOMNode $entityNode */
136
137 2
            $fieldMappings = array_merge($fieldMappings, $this->readFieldMappings(
138 2
                $entityNode,
139 2
                $mappingFile,
140 2
                null,
141 2
                false
142
            ));
143
        }
144
145 1
        return $fieldMappings;
146
    }
147
148 2
    private function createXPath(DOMNode $node): DOMXPath
149
    {
150
        /** @var DOMNode $ownerDocument */
151 2
        $ownerDocument = $node;
152
153 2
        if (!$ownerDocument instanceof DOMDocument) {
154 2
            $ownerDocument = $node->ownerDocument;
155
        }
156
157 2
        $xpath = new DOMXPath($ownerDocument);
158 2
        $xpath->registerNamespace('rdm', self::RDM_SCHEMA_URI);
159 2
        $xpath->registerNamespace('orm', self::DOCTRINE_SCHEMA_URI);
160
161 2
        return $xpath;
162
    }
163
164 1
    private function readObject(DOMNode $objectNode, string $mappingFile): ObjectMappingInterface
165
    {
166
        /** @var DOMNamedNodeMap $attributes */
167 1
        $objectNodeAttributes = $objectNode->attributes;
168
169 1
        if (is_null($objectNodeAttributes->getNamedItem("class"))) {
170
            throw new InvalidMappingException(sprintf(
171
                "Missing 'class' attribute on 'object' mapping in %s",
172
                $mappingFile
173
            ));
174
        }
175
176 1
        $className = (string)$objectNodeAttributes->getNamedItem("class")->nodeValue;
177
178
        /** @var CallDefinitionInterface|null $factory */
179 1
        $factory = null;
180
181
        /** @var CallDefinitionInterface|null $factory */
182 1
        $serializer = null;
183
184
        /** @var DOMXPath $xpath */
185 1
        $xpath = $this->createXPath($objectNode);
186
187 1
        foreach ($xpath->query('./rdm:factory', $objectNode) as $factoryNode) {
188
            /** @var DOMNode $factoryNode */
189
190
            /** @var array<MappingInterface> $argumentMappings */
191 1
            $argumentMappings = $this->readFieldMappings($factoryNode, $mappingFile);
192
193
            /** @var string $routineName */
194 1
            $routineName = (string)$factoryNode->attributes->getNamedItem('method')->nodeValue;
195
196
            /** @var string $objectReference */
197 1
            $objectReference = (string)$factoryNode->attributes->getNamedItem('object')->nodeValue;
198
199 1
            $factory = new CallDefinition(
200 1
                $this->kernel->getContainer(),
201 1
                $routineName,
202 1
                $objectReference,
203 1
                $argumentMappings
204
            );
205
        }
206
207 1
        if ($objectNodeAttributes->getNamedItem("factory") !== null && is_null($factory)) {
208 1
            $factory = $this->readCallDefinition(
209 1
                (string)$objectNodeAttributes->getNamedItem("factory")->nodeValue
210
            );
211
        }
212
213 1
        if ($objectNodeAttributes->getNamedItem("serialize") !== null) {
214 1
            $serializer = $this->readCallDefinition(
215 1
                (string)$objectNodeAttributes->getNamedItem("serialize")->nodeValue
216
            );
217
        }
218
219
        /** @var array<MappingInterface> $fieldMappings */
220 1
        $fieldMappings = $this->readFieldMappings($objectNode, $mappingFile);
221
222
        /** @var Column|null $dbalColumn */
223 1
        $dbalColumn = null;
224
225 1
        if ($objectNodeAttributes->getNamedItem("column") !== null) {
226
            /** @var bool $notnull */
227 1
            $notnull = true;
228
229
            /** @var string $type */
230 1
            $type = "string";
231
232
            /** @var int $length */
233 1
            $length = 255;
234
235 1
            if ($objectNodeAttributes->getNamedItem("nullable")) {
236 1
                $notnull = (strtolower($objectNodeAttributes->getNamedItem("nullable")->nodeValue) !== 'true');
237
            }
238
239 1
            if ($objectNodeAttributes->getNamedItem("column-type")) {
240 1
                $type = (string)$objectNodeAttributes->getNamedItem("column-type")->nodeValue;
241
            }
242
243 1
            if ($objectNodeAttributes->getNamedItem("column-length")) {
244
                $length = (int)$objectNodeAttributes->getNamedItem("column-length")->nodeValue;
245
            }
246
247 1
            $dbalColumn = new Column(
248 1
                (string)$objectNodeAttributes->getNamedItem("column")->nodeValue,
249 1
                Type::getType($type),
250
                [
251 1
                    'notnull' => $notnull,
252 1
                    'length' => $length
253
                ]
254
            );
255
        }
256
257
        /** @var string|null $id */
258 1
        $id = null;
259
260
        /** @var string|null $referencedId */
261 1
        $referencedId = null;
262
263 1
        if ($objectNodeAttributes->getNamedItem("id") !== null) {
264
            $id = (string)$objectNodeAttributes->getNamedItem("id")->nodeValue;
265
        }
266
267 1
        if ($objectNodeAttributes->getNamedItem("references-id") !== null) {
268
            $referencedId = (string)$objectNodeAttributes->getNamedItem("references-id")->nodeValue;
269
        }
270
271 1
        return new ObjectMapping(
272 1
            $className,
273 1
            $fieldMappings,
274 1
            $dbalColumn,
275 1
            sprintf(
276 1
                "in file '%s'",
277 1
                $mappingFile
278
            ),
279 1
            $factory,
280 1
            $serializer,
281 1
            $id,
282 1
            $referencedId
283
        );
284
    }
285
286 1
    private function readCallDefinition(string $callDefinition): CallDefinitionInterface
287
    {
288
        /** @var string $routineName */
289 1
        $routineName = $callDefinition;
290
291
        /** @var string|null $objectReference */
292 1
        $objectReference = null;
293
294
        /** @var bool $isStaticCall */
295 1
        $isStaticCall = false;
296
297 1
        if (strpos($callDefinition, '::') !== false) {
298 1
            [$objectReference, $routineName] = explode('::', $callDefinition);
299 1
            $isStaticCall = true;
300
        }
301
302 1
        if (strpos($callDefinition, '->') !== false) {
303
            [$objectReference, $routineName] = explode('->', $callDefinition);
304
        }
305
306 1
        return new CallDefinition(
307 1
            $this->kernel->getContainer(),
308 1
            $routineName,
309 1
            $objectReference,
310 1
            [],
311 1
            $isStaticCall
312
        );
313
    }
314
315 1
    private function readChoice(
316
        DOMNode $choiceNode,
317
        string $mappingFile,
318
        string $defaultColumnName
319
    ): ChoiceMappingInterface {
320
        /** @var string|Column $columnName */
321 1
        $column = $defaultColumnName;
322
323 1
        if (!is_null($choiceNode->attributes->getNamedItem("column"))) {
324 1
            $column = (string)$choiceNode->attributes->getNamedItem("column")->nodeValue;
325
        }
326
327
        /** @var array<MappingInterface> $choiceMappings */
328 1
        $choiceMappings = array();
329
330
        /** @var DOMXPath $xpath */
331 1
        $xpath = $this->createXPath($choiceNode);
332
333 1
        foreach ($xpath->query('./rdm:option', $choiceNode) as $optionNode) {
334
            /** @var DOMNode $optionNode */
335
336
            /** @var string $determinator */
337 1
            $determinator = (string)$optionNode->attributes->getNamedItem("name")->nodeValue;
338
339
            /** @var string $optionDefaultColumnName */
340 1
            $optionDefaultColumnName = sprintf("%s_%s", $defaultColumnName, $determinator);
341
342 1
            foreach ($this->readFieldMappings($optionNode, $mappingFile, $optionDefaultColumnName) as $mapping) {
343
                /** @var MappingInterface $mapping */
344
345 1
                $choiceMappings[$determinator] = $mapping;
346
            }
347
        }
348
349 1
        foreach ($xpath->query('./orm:field', $choiceNode) as $fieldNode) {
350
            /** @var DOMNode $fieldNode */
351
352 1
            $column = $this->readDoctrineField($fieldNode);
353
        }
354
355 1
        return new ChoiceMapping($column, $choiceMappings, sprintf(
356 1
            "in file '%s'",
357 1
            $mappingFile
358
        ));
359
    }
360
361
    /**
362
     * @return array<MappingInterface>
363
     */
364 2
    private function readFieldMappings(
365
        DOMNode $parentNode,
366
        string $mappingFile,
367
        string $choiceDefaultColumnName = null,
368
        bool $readFields = true
369
    ): array {
370
        /** @var DOMXPath $xpath */
371 2
        $xpath = $this->createXPath($parentNode);
372
373
        /** @var array<MappingInterface> $fieldMappings */
374 2
        $fieldMappings = array();
375
376 2
        foreach ($xpath->query('./rdm:service', $parentNode) as $serviceNode) {
377
            /** @var DOMNode $serviceNode */
378
379 1
            $serviceMapping = $this->readService($serviceNode, $mappingFile);
380
381 1
            if (!is_null($serviceNode->attributes->getNamedItem("field"))) {
382
                /** @var string $fieldName */
383 1
                $fieldName = (string)$serviceNode->attributes->getNamedItem("field")->nodeValue;
384
385 1
                $fieldMappings[$fieldName] = $serviceMapping;
386
387
            } else {
388 1
                $fieldMappings[] = $serviceMapping;
389
            }
390
        }
391
392 2
        foreach ($xpath->query('./rdm:choice', $parentNode) as $choiceNode) {
393
            /** @var DOMNode $choiceNode */
394
395
            /** @var string $defaultColumnName */
396 1
            $defaultColumnName = "";
397
398 1
            if (!is_null($choiceDefaultColumnName)) {
399
                $defaultColumnName = $choiceDefaultColumnName;
400
401 1
            } elseif (!is_null($choiceNode->attributes->getNamedItem("field"))) {
402 1
                $defaultColumnName = (string)$choiceNode->attributes->getNamedItem("field")->nodeValue;
403
            }
404
405 1
            $choiceMapping = $this->readChoice($choiceNode, $mappingFile, $defaultColumnName);
406
407 1
            if (!is_null($choiceNode->attributes->getNamedItem("field"))) {
408
                /** @var string $fieldName */
409 1
                $fieldName = (string)$choiceNode->attributes->getNamedItem("field")->nodeValue;
410
411 1
                $fieldMappings[$fieldName] = $choiceMapping;
412
413
            } else {
414 1
                $fieldMappings[] = $choiceMapping;
415
            }
416
        }
417
418 2
        foreach ($xpath->query('./rdm:object', $parentNode) as $objectNode) {
419
            /** @var DOMNode $objectNode */
420
421
            /** @var ObjectMappingInterface $objectMapping */
422 1
            $objectMapping = $this->readObject($objectNode, $mappingFile);
423
424 1
            if (!is_null($objectNode->attributes->getNamedItem("field"))) {
425
                /** @var string $fieldName */
426 1
                $fieldName = (string)$objectNode->attributes->getNamedItem("field")->nodeValue;
427
428 1
                $fieldMappings[$fieldName] = $objectMapping;
429
430
            } else {
431 1
                $fieldMappings[] = $objectMapping;
432
            }
433
        }
434
435 2
        if ($readFields) {
436 1
            foreach ($xpath->query('./orm:field', $parentNode) as $fieldNode) {
437
                /** @var DOMNode $fieldNode */
438
439
                /** @var Column $column */
440 1
                $column = $this->readDoctrineField($fieldNode);
441
442 1
                $fieldName = (string)$fieldNode->attributes->getNamedItem('name')->nodeValue;
443
444 1
                $fieldMappings[$fieldName] = new FieldMapping(
445 1
                    $column,
446 1
                    sprintf("in file '%s'", $mappingFile)
447
                );
448
            }
449
        }
450
451 2
        foreach ($xpath->query('./rdm:array', $parentNode) as $arrayNode) {
452
            /** @var DOMNode $arrayNode */
453
454
            /** @var ArrayMappingInterface $arrayMapping */
455 1
            $arrayMapping = $this->readArray($arrayNode, $mappingFile);
456
457 1
            if (!is_null($arrayNode->attributes->getNamedItem("field"))) {
458
                /** @var string $fieldName */
459 1
                $fieldName = (string)$arrayNode->attributes->getNamedItem("field")->nodeValue;
460
461 1
                $fieldMappings[$fieldName] = $arrayMapping;
462
463
            } else {
464 1
                $fieldMappings[] = $arrayMapping;
465
            }
466
        }
467
468 2
        foreach ($xpath->query('./rdm:list', $parentNode) as $listNode) {
469
            /** @var DOMNode $listNode */
470
471
            /** @var string $defaultColumnName */
472 1
            $defaultColumnName = "";
473
474 1
            if (!is_null($choiceDefaultColumnName)) {
475
                $defaultColumnName = $choiceDefaultColumnName;
476
477 1
            } elseif (!is_null($listNode->attributes->getNamedItem("field"))) {
478 1
                $defaultColumnName = (string)$listNode->attributes->getNamedItem("field")->nodeValue;
479
            }
480
481
            /** @var ArrayMappingInterface $listMapping */
482 1
            $listMapping = $this->readList($listNode, $mappingFile, $defaultColumnName);
483
484 1
            if (!is_null($listNode->attributes->getNamedItem("field"))) {
485
                /** @var string $fieldName */
486 1
                $fieldName = (string)$listNode->attributes->getNamedItem("field")->nodeValue;
487
488 1
                $fieldMappings[$fieldName] = $listMapping;
489
490
            } else {
491 1
                $fieldMappings[] = $listMapping;
492
            }
493
        }
494
495 2
        foreach ($xpath->query('./rdm:null', $parentNode) as $nullNode) {
496
            /** @var DOMNode $nullNode */
497
498 1
            if (!is_null($nullNode->attributes->getNamedItem("field"))) {
499
                /** @var string $fieldName */
500
                $fieldName = (string)$nullNode->attributes->getNamedItem("field")->nodeValue;
501
502
                $fieldMappings[$fieldName] = new NullMapping("in file '{$mappingFile}'");
503
504
            } else {
505 1
                $fieldMappings[] = new NullMapping("in file '{$mappingFile}'");
506
            }
507
        }
508
509 2
        foreach ($xpath->query('./rdm:nullable', $parentNode) as $nullableNode) {
510
            /** @var DOMNode $nullableNode */
511
512
            /** @var NullableMapping $nullableMapping */
513 1
            $nullableMapping = $this->readNullable($nullableNode, $mappingFile);
514
515 1
            if (!is_null($nullableNode->attributes->getNamedItem("field"))) {
516
                /** @var string $fieldName */
517 1
                $fieldName = (string)$nullableNode->attributes->getNamedItem("field")->nodeValue;
518
519 1
                $fieldMappings[$fieldName] = $nullableMapping;
520
521
            } else {
522 1
                $fieldMappings[] = $nullableMapping;
523
            }
524
        }
525
526 2
        foreach ($xpath->query('./rdm:import', $parentNode) as $importNode) {
527
            /** @var DOMNode $importNode */
528
529
            /** @var string $path */
530 2
            $path = (string)$importNode->attributes->getNamedItem("path")->nodeValue;
531
532
            /** @var string $forcedFieldName */
533 2
            $forcedFieldName = null;
534
535 2
            if (!is_null($importNode->attributes->getNamedItem("field"))) {
536 1
                $forcedFieldName = (string)$importNode->attributes->getNamedItem("field")->nodeValue;
537
            }
538
539 2
            foreach ($this->readFieldMappingsFromFile($path, $mappingFile) as $fieldName => $fieldMapping) {
540
                /** @var MappingInterface $fieldMapping */
541
542 1
                if (!empty($forcedFieldName)) {
543 1
                    $fieldMappings[$forcedFieldName] = $fieldMapping;
544
                } else {
545
546 1
                    $fieldMappings[$fieldName] = $fieldMapping;
547
                }
548
            }
549
        }
550
551 2
        return $fieldMappings;
552
    }
553
554 1
    private function readService(DOMNode $serviceNode, string $mappingFile): ServiceMappingInterface
555
    {
556
        /** @var bool $lax */
557 1
        $lax = false;
558
559 1
        if ($serviceNode->attributes->getNamedItem("lax") instanceof DOMNode) {
560 1
            $lax = strtolower($serviceNode->attributes->getNamedItem("lax")->nodeValue) === 'true';
561
        }
562
563
        /** @var string $serviceId */
564 1
        $serviceId = (string)$serviceNode->attributes->getNamedItem("id")->nodeValue;
565
566 1
        return new ServiceMapping(
567 1
            $this->kernel->getContainer(),
568 1
            $serviceId,
569 1
            $lax,
570 1
            sprintf(
571 1
                "in file '%s'",
572 1
                $mappingFile
573
            )
574
        );
575
    }
576
577 1
    private function readArray(DOMNode $arrayNode, string $mappingFile): ArrayMappingInterface
578
    {
579
        /** @var array<MappingInterface> $entryMappings */
580 1
        $entryMappings = $this->readFieldMappings($arrayNode, $mappingFile);
581
582
        /** @var DOMXPath $xpath */
583 1
        $xpath = $this->createXPath($arrayNode);
584
585 1
        foreach ($xpath->query('./rdm:entry', $arrayNode) as $entryNode) {
586
            /** @var DOMNode $entryNode */
587
588
            /** @var string|null $key */
589 1
            $key = null;
590
591 1
            if ($entryNode->attributes->getNamedItem("key") instanceof DOMNode) {
592 1
                $key = (string)$entryNode->attributes->getNamedItem("key")->nodeValue;
593
            }
594
595 1
            foreach ($this->readFieldMappings($entryNode, $mappingFile) as $entryMapping) {
596
                /** @var MappingInterface $entryMapping */
597
598 1
                if (is_null($key)) {
599
                    $entryMappings[] = $entryMapping;
600
601
                } else {
602 1
                    $entryMappings[$key] = $entryMapping;
603
                }
604
605 1
                break;
606
            }
607
        }
608
609 1
        return new ArrayMapping($entryMappings, sprintf(
610 1
            "in file '%s'",
611 1
            $mappingFile
612
        ));
613
    }
614
615 1
    private function readList(
616
        DOMNode $listNode,
617
        string $mappingFile,
618
        string $columnName
619
    ): ListMappingInterface {
620 1
        if (!is_null($listNode->attributes->getNamedItem("column"))) {
621 1
            $columnName = (string)$listNode->attributes->getNamedItem("column")->nodeValue;
622
        }
623
624
        /** @var array<MappingInterface> $entryMappings */
625 1
        $entryMappings = $this->readFieldMappings($listNode, $mappingFile);
626
627 1
        $column = new Column(
628 1
            $columnName,
629 1
            Type::getType("string"),
630 1
            []
631
        );
632
633 1
        return new ListMapping($column, array_values($entryMappings)[0], sprintf(
634 1
            "in file '%s'",
635 1
            $mappingFile
636
        ));
637
    }
638
639 1
    private function readNullable(
640
        DOMNode $nullableNode,
641
        string $mappingFile
642
    ): NullableMappingInterface {
643
        /** @var array<MappingInterface> $innerMappings */
644 1
        $innerMappings = $this->readFieldMappings($nullableNode, $mappingFile);
645
646 1
        if (count($innerMappings) !== 1) {
647
            throw new InvalidMappingException(sprintf(
648
                "A nullable mapping can only contain one inner mapping in '%s'!",
649
                $mappingFile
650
            ));
651
        }
652
653
        /** @var MappingInterface $innerMapping */
654 1
        $innerMapping = array_values($innerMappings)[0];
655
656
        /** @var Column|null $column */
657 1
        $column = null;
658
659 1
        if (!is_null($nullableNode->attributes->getNamedItem("column"))) {
660
            /** @var string $columnName */
661 1
            $columnName = (string)$nullableNode->attributes->getNamedItem("column")->nodeValue;
662
663 1
            $column = new Column(
664 1
                $columnName,
665 1
                Type::getType("boolean"),
666
                [
667 1
                    'notnull' => false
668
                ]
669
            );
670
        }
671
672 1
        return new NullableMapping($innerMapping, $column, sprintf(
673 1
            "in file '%s'",
674 1
            $mappingFile
675
        ));
676
    }
677
678 1
    private function readDoctrineField(DOMNode $fieldNode): Column
679
    {
680
        /** @var array<string> $attributes */
681 1
        $attributes = array();
682
683
        /** @var array<string> $keyMap */
684
        $keyMap = array(
685 1
            'column'            => 'name',
686
            'type'              => 'type',
687
            'nullable'          => 'notnull',
688
            'length'            => 'length',
689
            'precision'         => 'precision',
690
            'scale'             => 'scale',
691
            'column-definition' => 'columnDefinition',
692
        );
693
694
        /** @var string $columnName */
695 1
        $columnName = null;
696
697
        /** @var Type $type */
698 1
        $type = Type::getType('string');
699
700 1
        foreach ($fieldNode->attributes as $key => $attribute) {
701
            /** @var DOMAttr $attribute */
702
703 1
            $attributeValue = (string)$attribute->nodeValue;
704
705 1
            if ($key === 'column') {
706
                $columnName = $attributeValue;
707
708 1
            } elseif ($key === 'name') {
709 1
                if (empty($columnName)) {
710 1
                    $columnName = $attributeValue;
711
                }
712
713 1
            } elseif ($key === 'type') {
714 1
                $type = Type::getType($attributeValue);
715
716 1
            } elseif (isset($keyMap[$key])) {
717 1
                if ($key === 'nullable') {
718
                    # target is 'notnull', so falue is reversed
719 1
                    $attributeValue = ($attributeValue === 'false');
720
                }
721
722 1
                $attributes[$keyMap[$key]] = $attributeValue;
723
            }
724
        }
725
726 1
        $column = new Column(
727 1
            $columnName,
728 1
            $type,
729 1
            $attributes
730
        );
731
732 1
        return $column;
733
    }
734
735
}
736