Completed
Push — master ( 13b514...df0b53 )
by Gerrit
05:29
created

MappingXmlDriver::readService()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 2

Importance

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