Completed
Push — master ( 840912...a35e08 )
by Gerrit
03:16
created

MappingXmlDriver::readFieldMappings()   F

Complexity

Conditions 26
Paths > 20000

Size

Total Lines 201
Code Lines 90

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 72
CRAP Score 26.1847

Importance

Changes 0
Metric Value
dl 0
loc 201
ccs 72
cts 77
cp 0.9351
rs 2
c 0
b 0
f 0
cc 26
eloc 90
nc 361179
nop 4
crap 26.1847

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