Passed
Push — master ( e05469...bc3d5f )
by Gerrit
01:08 queued 10s
created

MappingXmlDriver::readArray()   A

Complexity

Conditions 5
Paths 7

Size

Total Lines 37

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 5.0073

Importance

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