Passed
Push — master ( 356755...14835f )
by Gerrit
04:25
created

MappingXmlDriver::readObject()   D

Complexity

Conditions 11
Paths 161

Size

Total Lines 108
Code Lines 51

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 46
CRAP Score 11.1138

Importance

Changes 0
Metric Value
dl 0
loc 108
ccs 46
cts 51
cp 0.902
rs 4.9629
c 0
b 0
f 0
cc 11
eloc 51
nc 161
nop 2
crap 11.1138

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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