Passed
Push — master ( 064bed...d815ea )
by Gerrit
04:18
created

MappingXmlDriver::readObject()   F

Complexity

Conditions 14
Paths 545

Size

Total Lines 136
Code Lines 69

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 48
CRAP Score 14.1643

Importance

Changes 0
Metric Value
cc 14
eloc 69
c 0
b 0
f 0
nc 545
nop 2
dl 0
loc 136
ccs 48
cts 53
cp 0.9057
crap 14.1643
rs 2.8619

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 DOMAttr;
17
use DOMNamedNodeMap;
18
use Doctrine\DBAL\Schema\Column;
19
use Doctrine\DBAL\Types\Type;
20
use Doctrine\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
use DOMElement;
41
use Webmozart\Assert\Assert;
42
43
final class MappingXmlDriver implements MappingDriverInterface
44
{
45
46
    const RDM_SCHEMA_URI = "http://github.com/addiks/symfony_rdm/tree/master/Resources/mapping-schema.v1.xsd";
47
    const DOCTRINE_SCHEMA_URI = "http://doctrine-project.org/schemas/orm/doctrine-mapping";
48
49
    /**
50
     * @var FileLocator
51
     */
52
    private $doctrineFileLocator;
53
54
    /**
55
     * @var KernelInterface
56
     */
57
    private $kernel;
58
59
    /**
60
     * @var ContainerInterface
61
     */
62
    private $serviceContainer;
63
64
    /**
65
     * @var string
66
     */
67
    private $schemaFilePath;
68
69 3
    public function __construct(
70
        FileLocator $doctrineFileLocator,
71
        KernelInterface $kernel,
72
        string $schemaFilePath
73
    ) {
74
        /** @var ContainerInterface|null $serviceContainer */
75 3
        $serviceContainer = $kernel->getContainer();
76
77 3
        if (is_null($serviceContainer)) {
78
            throw new ErrorException("Kernel does not have a container!");
79
        }
80
81 3
        $this->doctrineFileLocator = $doctrineFileLocator;
82 3
        $this->kernel = $kernel;
83 3
        $this->schemaFilePath = $schemaFilePath;
84 3
        $this->serviceContainer = $serviceContainer;
85
    }
86
87 2
    public function loadRDMMetadataForClass(string $className): ?EntityMappingInterface
88
    {
89
        /** @var ?EntityMappingInterface $mapping */
90 2
        $mapping = null;
91
92
        /** @var array<MappingInterface> $fieldMappings */
93 2
        $fieldMappings = array();
94
95 2
        if ($this->doctrineFileLocator->fileExists($className)) {
96
            /** @var string $mappingFile */
97 2
            $mappingFile = $this->doctrineFileLocator->findMappingFile($className);
98
99 2
            $fieldMappings = $this->readFieldMappingsFromFile($mappingFile);
100
        }
101
102 1
        if (!empty($fieldMappings)) {
103 1
            $mapping = new EntityMapping($className, $fieldMappings);
104
        }
105
106 1
        return $mapping;
107
    }
108
109
    /**
110
     * @return array<MappingInterface>
111
     */
112 2
    private function readFieldMappingsFromFile(string $mappingFile, string $parentMappingFile = null): array
113
    {
114 2
        if ($mappingFile[0] === '@') {
115
            /** @var string $mappingFile */
116 1
            $mappingFile = $this->kernel->locateResource($mappingFile);
117
        }
118
119 2
        if ($mappingFile[0] !== DIRECTORY_SEPARATOR && !empty($parentMappingFile)) {
120 1
            $mappingFile = dirname($parentMappingFile) . DIRECTORY_SEPARATOR . $mappingFile;
121
        }
122
123 2
        if (!file_exists($mappingFile)) {
124 1
            throw new InvalidMappingException(sprintf(
125
                "Missing referenced orm file '%s'%s!",
126
                $mappingFile,
127 1
                is_string($parentMappingFile) ?sprintf(", referenced in file '%s'", $parentMappingFile) :''
128
            ));
129
        }
130
        
131
        /** @var string|null $mappingXml */
132 2
        $mappingXml = file_get_contents($mappingFile);
133
        
134 2
        Assert::notEmpty($mappingXml, sprintf('ORM-Mapping file "%s" is empty!', $mappingFile));
135
136 2
        $dom = new DOMDocument();
137 2
        $dom->loadXML($mappingXml);
138
139
        /** @var DOMXPath $xpath */
140 2
        $xpath = $this->createXPath($dom->documentElement);
141
142
        /** @var array<MappingInterface> $fieldMappings */
143 2
        $fieldMappings = $this->readFieldMappings(
144
            $dom,
145
            $mappingFile,
146
            null,
147
            false
148
        );
149
150 2
        foreach ($xpath->query("//orm:entity", $dom) as $entityNode) {
151
            /** @var DOMNode $entityNode */
152
153 2
            $fieldMappings = array_merge($fieldMappings, $this->readFieldMappings(
154
                $entityNode,
155
                $mappingFile,
156
                null,
157
                false
158
            ));
159
        }
160
161 1
        return $fieldMappings;
162
    }
163
164 2
    private function createXPath(DOMNode $node): DOMXPath
165
    {
166
        /** @var DOMNode $ownerDocument */
167 2
        $ownerDocument = $node;
168
169 2
        if (!$ownerDocument instanceof DOMDocument) {
170 2
            $ownerDocument = $node->ownerDocument;
171 2
            Assert::object($ownerDocument);
172
        }
173
174 2
        $xpath = new DOMXPath($ownerDocument);
0 ignored issues
show
Bug introduced by
It seems like $ownerDocument can also be of type null; however, parameter $document of DOMXPath::__construct() does only seem to accept DOMDocument, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

174
        $xpath = new DOMXPath(/** @scrutinizer ignore-type */ $ownerDocument);
Loading history...
175 2
        $xpath->registerNamespace('rdm', self::RDM_SCHEMA_URI);
176 2
        $xpath->registerNamespace('orm', self::DOCTRINE_SCHEMA_URI);
177
178 2
        return $xpath;
179
    }
180
181 1
    private function readObject(DOMNode $objectNode, string $mappingFile): ObjectMapping
182
    {
183
        /** @var DOMNamedNodeMap|null $attributes */
184 1
        $objectNodeAttributes = $objectNode->attributes;
0 ignored issues
show
Unused Code introduced by
The assignment to $objectNodeAttributes is dead and can be removed.
Loading history...
185
186 1
        if (!$this->hasAttributeValue($objectNode, "class")) {
187
            throw new InvalidMappingException(sprintf(
188
                "Missing 'class' attribute on 'object' mapping in %s in line %d",
189
                $mappingFile,
190
                $objectNode->getLineNo()
191
            ));
192
        }
193
194
        /** @var class-string $className */
195 1
        $className = (string)$this->readAttributeValue($objectNode, "class");
196
197 1
        Assert::true(class_exists($className) || interface_exists($className));
198
199
        /** @var CallDefinitionInterface|null $factory */
200 1
        $factory = null;
201
202
        /** @var CallDefinitionInterface|null $factory */
203 1
        $serializer = null;
204
205
        /** @var DOMXPath $xpath */
206 1
        $xpath = $this->createXPath($objectNode);
207
208 1
        foreach ($xpath->query('./rdm:factory', $objectNode) as $factoryNode) {
209
            /** @var DOMNode $factoryNode */
210
211
            /** @var array<MappingInterface> $argumentMappings */
212 1
            $argumentMappings = $this->readFieldMappings($factoryNode, $mappingFile);
213
214
            /** @var string $routineName */
215 1
            $routineName = (string)$this->readAttributeValue($factoryNode, "method");
216
217
            /** @var string $objectReference */
218 1
            $objectReference = (string)$this->readAttributeValue($factoryNode, "object");
219
220 1
            $factory = new CallDefinition(
221 1
                $this->serviceContainer,
222
                $routineName,
223
                $objectReference,
224
                $argumentMappings,
225
                false,
226 1
                $mappingFile . " in line " . $objectNode->getLineNo()
227
            );
228
        }
229
230 1
        if ($this->hasAttributeValue($objectNode, "factory") && is_null($factory)) {
231 1
            $factory = $this->readCallDefinition(
232 1
                (string)$this->readAttributeValue($objectNode, "factory"),
233 1
                $mappingFile . " in line " . $objectNode->getLineNo()
234
            );
235
        }
236
237 1
        if ($this->hasAttributeValue($objectNode, "serialize")) {
238 1
            $serializer = $this->readCallDefinition(
239 1
                (string)$this->readAttributeValue($objectNode, "serialize"),
240 1
                $mappingFile . " in line " . $objectNode->getLineNo()
241
            );
242
        }
243
244
        /** @var array<MappingInterface> $fieldMappings */
245 1
        $fieldMappings = $this->readFieldMappings($objectNode, $mappingFile);
246
247
        /** @var Column|null $dbalColumn */
248 1
        $dbalColumn = null;
249
250 1
        if ($this->hasAttributeValue($objectNode, "column")) {
251
            /** @var bool $notnull */
252 1
            $notnull = true;
253
254
            /** @var string $type */
255 1
            $type = "string";
256
257
            /** @var int $length */
258 1
            $length = 255;
259
260
            /** @var string|null $default */
261 1
            $default = null;
262
263 1
            if ($this->hasAttributeValue($objectNode, "nullable")) {
264 1
                $notnull = (strtolower((string)$this->readAttributeValue($objectNode, "nullable")) !== 'true');
265
            }
266
267 1
            if ($this->hasAttributeValue($objectNode, "column-type")) {
268 1
                $type = (string)$this->readAttributeValue($objectNode, "column-type");
269
            }
270
271 1
            if ($this->hasAttributeValue($objectNode, "column-length")) {
272
                $length = (string)$this->readAttributeValue($objectNode, "column-length");
273
            }
274
275 1
            if ($this->hasAttributeValue($objectNode, "column-default")) {
276 1
                $default = (string)$this->readAttributeValue($objectNode, "column-default");
277
            }
278
279 1
            $dbalColumn = new Column(
280 1
                (string)$this->readAttributeValue($objectNode, "column"),
281 1
                Type::getType($type),
282
                [
283 1
                    'notnull' => $notnull,
284
                    'length' => $length,
285
                    'default' => $default
286
                ]
287
            );
288
        }
289
290
        /** @var string|null $id */
291 1
        $id = null;
292
293
        /** @var string|null $referencedId */
294 1
        $referencedId = null;
295
296 1
        if ($this->hasAttributeValue($objectNode, "id")) {
297
            $id = (string)$this->readAttributeValue($objectNode, "id");
298
        }
299
300 1
        if ($this->hasAttributeValue($objectNode, "references-id")) {
301
            $referencedId = (string)$this->readAttributeValue($objectNode, "references-id");
302
        }
303
304 1
        return new ObjectMapping(
305
            $className,
306
            $fieldMappings,
307
            $dbalColumn,
308 1
            sprintf(
309
                "in file '%s' in line %d",
310
                $mappingFile,
311 1
                $objectNode->getLineNo()
312
            ),
313
            $factory,
314
            $serializer,
315
            $id,
316
            $referencedId
317
        );
318
    }
319
320 1
    private function readCallDefinition(
321
        string $callDefinition,
322
        string $origin = "unknown"
323
    ): CallDefinitionInterface {
324
        /** @var string $routineName */
325 1
        $routineName = $callDefinition;
326
327
        /** @var string|null $objectReference */
328 1
        $objectReference = null;
329
330
        /** @var bool $isStaticCall */
331 1
        $isStaticCall = false;
332
333 1
        if (strpos($callDefinition, '::') !== false) {
334 1
            [$objectReference, $routineName] = explode('::', $callDefinition);
335 1
            $isStaticCall = true;
336
        }
337
338 1
        if (strpos($callDefinition, '->') !== false) {
339
            [$objectReference, $routineName] = explode('->', $callDefinition);
340
        }
341
342 1
        return new CallDefinition(
343 1
            $this->serviceContainer,
344
            $routineName,
345
            $objectReference,
346 1
            [],
347
            $isStaticCall,
348
            $origin
349
        );
350
    }
351
352 1
    private function readChoice(
353
        DOMNode $choiceNode,
354
        string $mappingFile,
355
        string $defaultColumnName
356
    ): ChoiceMapping {
357
        /** @var string|Column $columnName */
358 1
        $column = $defaultColumnName;
359
360 1
        if ($this->hasAttributeValue($choiceNode, "column")) {
361 1
            $column = (string)$this->readAttributeValue($choiceNode, "column");
362
        }
363
364
        /** @var array<MappingInterface> $choiceMappings */
365 1
        $choiceMappings = array();
366
367
        /** @var DOMXPath $xpath */
368 1
        $xpath = $this->createXPath($choiceNode);
369
370 1
        foreach ($xpath->query('./rdm:option', $choiceNode) as $optionNode) {
371
            /** @var DOMNode $optionNode */
372
373
            /** @var string $determinator */
374 1
            $determinator = (string)$this->readAttributeValue($optionNode, "name");
375
376
            /** @var string $optionDefaultColumnName */
377 1
            $optionDefaultColumnName = sprintf("%s_%s", $defaultColumnName, $determinator);
378
379 1
            foreach ($this->readFieldMappings($optionNode, $mappingFile, $optionDefaultColumnName) as $mapping) {
380
                /** @var MappingInterface $mapping */
381
382 1
                $choiceMappings[$determinator] = $mapping;
383
            }
384
        }
385
386 1
        foreach ($xpath->query('./orm:field', $choiceNode) as $fieldNode) {
387
            /** @var DOMNode $fieldNode */
388
389 1
            $column = $this->readDoctrineField($fieldNode);
390
        }
391
392 1
        return new ChoiceMapping($column, $choiceMappings, sprintf(
393
            "in file '%s' in line %d",
394
            $mappingFile,
395 1
            $choiceNode->getLineNo()
396
        ));
397
    }
398
399
    /**
400
     * @return array<MappingInterface>
401
     */
402 2
    private function readFieldMappings(
403
        DOMNode $parentNode,
404
        string $mappingFile,
405
        string $choiceDefaultColumnName = null,
406
        bool $readFields = true
407
    ): array {
408
        /** @var DOMXPath $xpath */
409 2
        $xpath = $this->createXPath($parentNode);
410
411
        /** @var array<MappingInterface> $fieldMappings */
412 2
        $fieldMappings = array();
413
414 2
        foreach ($xpath->query('./rdm:service', $parentNode) as $serviceNode) {
415
            /** @var DOMNode $serviceNode */
416
417 1
            $serviceMapping = $this->readService($serviceNode, $mappingFile);
418
419 1
            if ($this->hasAttributeValue($serviceNode, "field")) {
420
                /** @var string $fieldName */
421 1
                $fieldName = (string)$this->readAttributeValue($serviceNode, "field");
422
423 1
                $fieldMappings[$fieldName] = $serviceMapping;
424
425
            } else {
426 1
                $fieldMappings[] = $serviceMapping;
427
            }
428
        }
429
430 2
        foreach ($xpath->query('./rdm:choice', $parentNode) as $choiceNode) {
431
            /** @var DOMNode $choiceNode */
432
433
            /** @var string $defaultColumnName */
434 1
            $defaultColumnName = "";
435
436 1
            if (!is_null($choiceDefaultColumnName)) {
437
                $defaultColumnName = $choiceDefaultColumnName;
438
439 1
            } elseif ($this->hasAttributeValue($choiceNode, "field")) {
440 1
                $defaultColumnName = (string)$this->readAttributeValue($choiceNode, "field");
441
            }
442
443 1
            $choiceMapping = $this->readChoice($choiceNode, $mappingFile, $defaultColumnName);
444
445 1
            if ($this->hasAttributeValue($choiceNode, "field")) {
446
                /** @var string $fieldName */
447 1
                $fieldName = (string)$this->readAttributeValue($choiceNode, "field");
448
449 1
                $fieldMappings[$fieldName] = $choiceMapping;
450
451
            } else {
452 1
                $fieldMappings[] = $choiceMapping;
453
            }
454
        }
455
456 2
        foreach ($xpath->query('./rdm:object', $parentNode) as $objectNode) {
457
            /** @var DOMNode $objectNode */
458
459
            /** @var ObjectMapping $objectMapping */
460 1
            $objectMapping = $this->readObject($objectNode, $mappingFile);
461
462 1
            if ($this->hasAttributeValue($objectNode, "field")) {
463
                /** @var string $fieldName */
464 1
                $fieldName = (string)$this->readAttributeValue($objectNode, "field");
465
466 1
                $fieldMappings[$fieldName] = $objectMapping;
467
468
            } else {
469 1
                $fieldMappings[] = $objectMapping;
470
            }
471
        }
472
473 2
        if ($readFields) {
474 1
            foreach ($xpath->query('./orm:field', $parentNode) as $fieldNode) {
475
                /** @var DOMNode $fieldNode */
476
477
                /** @var Column $column */
478 1
                $column = $this->readDoctrineField($fieldNode);
479
480 1
                $fieldName = (string)$this->readAttributeValue($fieldNode, "name");
481
482 1
                $fieldMappings[$fieldName] = new FieldMapping(
483
                    $column,
484 1
                    sprintf("in file '%s' in line %d", $mappingFile, $fieldNode->getLineNo())
485
                );
486
            }
487
        }
488
489 2
        foreach ($xpath->query('./rdm:array', $parentNode) as $arrayNode) {
490
            /** @var DOMNode $arrayNode */
491
492
            /** @var ArrayMapping $arrayMapping */
493 1
            $arrayMapping = $this->readArray($arrayNode, $mappingFile);
494
495 1
            if ($this->hasAttributeValue($arrayNode, "field")) {
496
                /** @var string $fieldName */
497 1
                $fieldName = (string)$this->readAttributeValue($arrayNode, "field");
498
499 1
                $fieldMappings[$fieldName] = $arrayMapping;
500
501
            } else {
502
                $fieldMappings[] = $arrayMapping;
503
            }
504
        }
505
506 2
        foreach ($xpath->query('./rdm:list', $parentNode) as $listNode) {
507
            /** @var DOMNode $listNode */
508
509
            /** @var string $defaultColumnName */
510 1
            $defaultColumnName = "";
511
512 1
            if (!is_null($choiceDefaultColumnName)) {
513
                $defaultColumnName = $choiceDefaultColumnName;
514
515 1
            } elseif ($this->hasAttributeValue($listNode, "field")) {
516 1
                $defaultColumnName = (string)$this->readAttributeValue($listNode, "field");
517
            }
518
519
            /** @var ListMapping $listMapping */
520 1
            $listMapping = $this->readList($listNode, $mappingFile, $defaultColumnName);
521
522 1
            if ($this->hasAttributeValue($listNode, "field")) {
523
                /** @var string $fieldName */
524 1
                $fieldName = (string)$this->readAttributeValue($listNode, "field");
525
526 1
                $fieldMappings[$fieldName] = $listMapping;
527
528
            } else {
529 1
                $fieldMappings[] = $listMapping;
530
            }
531
        }
532
533 2
        foreach ($xpath->query('./rdm:null', $parentNode) as $nullNode) {
534
            /** @var DOMNode $nullNode */
535
536 1
            if ($this->hasAttributeValue($nullNode, "field")) {
537
                /** @var string $fieldName */
538
                $fieldName = (string)$this->readAttributeValue($nullNode, "field");
539
540
                $fieldMappings[$fieldName] = new NullMapping(sprintf(
541
                    "in file '%s' in line %d",
542
                    $mappingFile,
543
                    $nullNode->getLineNo()
544
                ));
545
546
            } else {
547 1
                $fieldMappings[] = new NullMapping(sprintf(
548
                    "in file '%s' in line %d",
549
                    $mappingFile,
550 1
                    $nullNode->getLineNo()
551
                ));
552
            }
553
        }
554
555 2
        foreach ($xpath->query('./rdm:nullable', $parentNode) as $nullableNode) {
556
            /** @var DOMNode $nullableNode */
557
558
            /** @var NullableMapping $nullableMapping */
559 1
            $nullableMapping = $this->readNullable($nullableNode, $mappingFile);
560
561 1
            if ($this->hasAttributeValue($nullableNode, "field")) {
562
                /** @var string $fieldName */
563 1
                $fieldName = (string)$this->readAttributeValue($nullableNode, "field");
564
565 1
                $fieldMappings[$fieldName] = $nullableMapping;
566
567
            } else {
568
                $fieldMappings[] = $nullableMapping;
569
            }
570
        }
571
572 2
        foreach ($xpath->query('./rdm:import', $parentNode) as $importNode) {
573
            /** @var DOMNode $importNode */
574
575
            /** @var string $path */
576 2
            $path = (string)$this->readAttributeValue($importNode, "path");
577
578
            /** @var string|null $forcedFieldName */
579 2
            $forcedFieldName = $this->readAttributeValue($importNode, "field");
580
581
            /** @var string $columnPrefix */
582 2
            $columnPrefix = (string)$this->readAttributeValue($importNode, "column-prefix");
583
584 2
            foreach ($this->readFieldMappingsFromFile($path, $mappingFile) as $fieldName => $fieldMapping) {
585
                /** @var MappingInterface $fieldMapping */
586
587 1
                $fieldMappingProxy = new MappingProxy(
0 ignored issues
show
Unused Code introduced by
The assignment to $fieldMappingProxy is dead and can be removed.
Loading history...
588
                    $fieldMapping,
589
                    $columnPrefix
590
                );
591
592 1
                if (!empty($forcedFieldName)) {
593 1
                    $fieldMappings[$forcedFieldName] = $fieldMapping;
594
                } else {
595
596
                    $fieldMappings[$fieldName] = $fieldMapping;
597
                }
598
            }
599
        }
600
601 2
        return $fieldMappings;
602
    }
603
604 1
    private function readService(DOMNode $serviceNode, string $mappingFile): ServiceMapping
605
    {
606
        /** @var bool $lax */
607 1
        $lax = strtolower((string)$this->readAttributeValue($serviceNode, "lax")) === 'true';
608
609
        /** @var string $serviceId */
610 1
        $serviceId = (string)$this->readAttributeValue($serviceNode, "id");
611
612 1
        return new ServiceMapping(
613 1
            $this->serviceContainer,
614
            $serviceId,
615
            $lax,
616 1
            sprintf(
617
                "in file '%s' in line %d",
618
                $mappingFile,
619 1
                $serviceNode->getLineNo()
620
            )
621
        );
622
    }
623
624 1
    private function readArray(DOMNode $arrayNode, string $mappingFile): ArrayMapping
625
    {
626
        /** @var array<MappingInterface> $entryMappings */
627 1
        $entryMappings = $this->readFieldMappings($arrayNode, $mappingFile);
628
629
        /** @var DOMXPath $xpath */
630 1
        $xpath = $this->createXPath($arrayNode);
631
632 1
        foreach ($xpath->query('./rdm:entry', $arrayNode) as $entryNode) {
633
            /** @var DOMNode $entryNode */
634
635
            /** @var string|null $key */
636 1
            $key = $this->readAttributeValue($entryNode, "key");
637
638 1
            foreach ($this->readFieldMappings($entryNode, $mappingFile) as $entryMapping) {
639
                /** @var MappingInterface $entryMapping */
640
641 1
                if (is_null($key)) {
642
                    $entryMappings[] = $entryMapping;
643
644
                } else {
645 1
                    $entryMappings[$key] = $entryMapping;
646
                }
647
648 1
                break;
649
            }
650
        }
651
652 1
        return new ArrayMapping($entryMappings, sprintf(
653
            "in file '%s' in line %d",
654
            $mappingFile,
655 1
            $arrayNode->getLineNo()
656
        ));
657
    }
658
659 1
    private function readList(
660
        DOMNode $listNode,
661
        string $mappingFile,
662
        string $columnName
663
    ): ListMapping {
664 1
        if ($this->hasAttributeValue($listNode, "column")) {
665 1
            $columnName = (string)$this->readAttributeValue($listNode, "column");
666
        }
667
668
        /** @var array<MappingInterface> $entryMappings */
669 1
        $entryMappings = $this->readFieldMappings($listNode, $mappingFile);
670
671
        /** @var array<string, mixed> $columnOptions */
672 1
        $columnOptions = array();
673
674 1
        if ($this->hasAttributeValue($listNode, "column-length")) {
675
            $columnOptions['length'] = (int)$this->readAttributeValue($listNode, "column-length", "0");
676
        }
677
678 1
        $column = new Column(
679
            $columnName,
680 1
            Type::getType("string"),
681
            $columnOptions
682
        );
683
684 1
        return new ListMapping($column, array_values($entryMappings)[0], sprintf(
685
            "in file '%s' in line %d",
686
            $mappingFile,
687 1
            $listNode->getLineNo()
688
        ));
689
    }
690
691 1
    private function readNullable(
692
        DOMNode $nullableNode,
693
        string $mappingFile
694
    ): NullableMapping {
695
        /** @var array<MappingInterface> $innerMappings */
696 1
        $innerMappings = $this->readFieldMappings($nullableNode, $mappingFile);
697
698 1
        if (count($innerMappings) !== 1) {
699
            throw new InvalidMappingException(sprintf(
700
                "A nullable mapping must contain exactly one inner mapping in '%s' at line %d!",
701
                $mappingFile,
702
                $nullableNode->getLineNo()
703
            ));
704
        }
705
706
        /** @var MappingInterface $innerMapping */
707 1
        $innerMapping = array_values($innerMappings)[0];
708
709
        /** @var Column|null $column */
710 1
        $column = null;
711
712 1
        if ($this->hasAttributeValue($nullableNode, "column")) {
713
            /** @var string $columnName */
714 1
            $columnName = $this->readAttributeValue($nullableNode, "column", "");
715
716 1
            $column = new Column(
717
                $columnName,
718 1
                Type::getType("boolean"),
719
                [
720 1
                    'notnull' => false
721
                ]
722
            );
723
        }
724
725 1
        $strict = $this->readAttributeValue($nullableNode, "strict", "false") === "true" ? true : false;
726
727 1
        return new NullableMapping($innerMapping, $column, sprintf(
728
            "in file '%s' at line %d",
729
            $mappingFile,
730 1
            $nullableNode->getLineNo()
731
        ), $strict);
732
    }
733
734 1
    private function readDoctrineField(DOMNode $fieldNode): Column
735
    {
736
        /** @var array<string> $attributes */
737 1
        $attributes = array();
738
739
        /** @var array<string> $keyMap */
740 1
        $keyMap = array(
741
            'column'            => 'name',
742
            'type'              => 'type',
743
            'nullable'          => 'notnull',
744
            'length'            => 'length',
745
            'precision'         => 'precision',
746
            'scale'             => 'scale',
747
            'column-definition' => 'columnDefinition',
748
        );
749
750
        /** @var string|null $columnName */
751 1
        $columnName = null;
752
753
        /** @var Type $type */
754 1
        $type = Type::getType('string');
755
756
        /** @var DOMNamedNodeMap|null $fieldNodeAttributes */
757 1
        $fieldNodeAttributes = $fieldNode->attributes;
758
759 1
        if (is_object($fieldNodeAttributes)) {
760 1
            foreach ($fieldNodeAttributes as $key => $attribute) {
761
                /** @var DOMAttr $attribute */
762
763 1
                $attributeValue = $attribute->nodeValue;
764
765 1
                if ($key === 'column') {
766
                    $columnName = $attributeValue;
767
768 1
                } elseif ($key === 'name') {
769 1
                    if (empty($columnName)) {
770 1
                        $columnName = $attributeValue;
771
                    }
772
773 1
                } elseif ($key === 'type') {
774 1
                    $type = Type::getType((string) $attributeValue);
775
776 1
                } elseif (isset($keyMap[$key])) {
777 1
                    if ($key === 'nullable') {
778
                        # target is 'notnull', so falue is reversed
779 1
                        $attributeValue = ($attributeValue === 'false');
780
                    }
781
782 1
                    $attributes[$keyMap[$key]] = $attributeValue;
783
                }
784
            }
785
        }
786
        
787 1
        Assert::notEmpty($columnName, 'Column name cannot be empty!');
788
789 1
        $column = new Column(
790
            $columnName,
791
            $type,
792
            $attributes
793
        );
794
795 1
        return $column;
796
    }
797
798 1
    private function hasAttributeValue(DOMNode $node, string $attributeName): bool
799
    {
800
        /** @var DOMNamedNodeMap $nodeAttributes */
801 1
        $nodeAttributes = $node->attributes;
802
803
        /** @var DOMNode|null $attributeNode */
804 1
        $attributeNode = $nodeAttributes->getNamedItem($attributeName);
805
806 1
        return is_object($attributeNode);
807
    }
808
809 2
    private function readAttributeValue(DOMNode $node, string $attributeName, ?string $default = null): ?string
810
    {
811
        /** @var DOMNamedNodeMap $nodeAttributes */
812 2
        $nodeAttributes = $node->attributes;
813
814
        /** @var DOMNode|null $attributeNode */
815 2
        $attributeNode = $nodeAttributes->getNamedItem($attributeName);
816
817
        /** @var string|null $value */
818 2
        $value = $default;
819
820 2
        if (is_object($attributeNode)) {
821 2
            $value = $attributeNode->nodeValue;
822
        }
823
824 2
        return $value;
825
    }
826
827
}
828