Completed
Push — master ( 203e38...2cdd54 )
by Gerrit
13:04
created

MappingXmlDriver   B

Complexity

Total Complexity 46

Size/Duplication

Total Lines 432
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Test Coverage

Coverage 94.48%

Importance

Changes 0
Metric Value
wmc 46
lcom 1
cbo 10
dl 0
loc 432
ccs 154
cts 163
cp 0.9448
rs 8.3999
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
B loadRDMMetadataForClass() 0 61 7
A createXPath() 0 8 1
B readObject() 0 57 5
A readCallDefinition() 0 16 2
B readChoice() 0 45 5
F readFieldMappings() 0 103 12
A readService() 0 17 2
B readArray() 0 37 5
B readDoctrineField() 0 51 6

How to fix   Complexity   

Complex Class

Complex classes like MappingXmlDriver often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use MappingXmlDriver, and based on these observations, apply Extract Interface, too.

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
37
final class MappingXmlDriver implements MappingDriverInterface
38
{
39
40
    const RDM_SCHEMA_URI = "http://github.com/addiks/symfony_rdm/tree/master/Resources/mapping-schema.v1.xsd";
41
    const DOCTRINE_SCHEMA_URI = "http://doctrine-project.org/schemas/orm/doctrine-mapping";
42
43
    /**
44
     * @var FileLocator
45
     */
46
    private $fileLocator;
47
48
    /**
49
     * @var string
50
     */
51
    private $schemaFilePath;
52
53 2
    public function __construct(
54
        FileLocator $fileLocator,
55
        string $schemaFilePath
56
    ) {
57 2
        $this->fileLocator = $fileLocator;
58 2
        $this->schemaFilePath = $schemaFilePath;
59 2
    }
60
61 1
    public function loadRDMMetadataForClass(string $className): ?EntityMappingInterface
62
    {
63
        /** @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...
64 1
        $mapping = null;
65
66
        /** @var array<MappingInterface> $fieldMappings */
67 1
        $fieldMappings = array();
68
69 1
        if ($this->fileLocator->fileExists($className)) {
70
            /** @var string $mappingFile */
71 1
            $mappingFile = $this->fileLocator->findMappingFile($className);
72
73 1
            $dom = new DOMDocument();
74 1
            $dom->loadXML(file_get_contents($mappingFile));
75
76
            /** @var DOMXPath $xpath */
77 1
            $xpath = $this->createXPath($dom->documentElement);
78
79 1
            foreach ($xpath->query("//orm:entity/rdm:service", $dom) as $serviceNode) {
80
                /** @var DOMNode $serviceNode */
81
82
                /** @var string $fieldName */
83 1
                $fieldName = (string)$serviceNode->attributes->getNamedItem("field")->nodeValue;
84
85 1
                $fieldMappings[$fieldName] = $this->readService($serviceNode, $mappingFile);
86
            }
87
88 1
            foreach ($xpath->query("//orm:entity/rdm:choice", $dom) as $choiceNode) {
89
                /** @var DOMNode $choiceNode */
90
91
                /** @var string $fieldName */
92 1
                $fieldName = (string)$choiceNode->attributes->getNamedItem("field")->nodeValue;
93
94 1
                $fieldMappings[$fieldName] = $this->readChoice($choiceNode, $mappingFile, $fieldName);
95
            }
96
97 1
            foreach ($xpath->query("//orm:entity/rdm:object", $dom) as $objectNode) {
98
                /** @var DOMNode $objectNode */
99
100
                /** @var string $fieldName */
101 1
                $fieldName = (string)$objectNode->attributes->getNamedItem("field")->nodeValue;
102
103 1
                $fieldMappings[$fieldName] = $this->readObject($objectNode, $mappingFile);
104
            }
105
106 1
            foreach ($xpath->query("//orm:entity/rdm:array", $dom) as $arrayNode) {
107
                /** @var DOMNode $arrayNode */
108
109
                /** @var string $fieldName */
110 1
                $fieldName = (string)$arrayNode->attributes->getNamedItem("field")->nodeValue;
111
112 1
                $fieldMappings[$fieldName] = $this->readArray($arrayNode, $mappingFile);
113
            }
114
        }
115
116 1
        if (!empty($fieldMappings)) {
117 1
            $mapping = new EntityMapping($className, $fieldMappings);
118
        }
119
120 1
        return $mapping;
121
    }
122
123 1
    private function createXPath(DOMNode $node): DOMXPath
124
    {
125 1
        $xpath = new DOMXPath($node->ownerDocument);
126 1
        $xpath->registerNamespace('rdm', self::RDM_SCHEMA_URI);
127 1
        $xpath->registerNamespace('orm', self::DOCTRINE_SCHEMA_URI);
128
129 1
        return $xpath;
130
    }
131
132 1
    private function readObject(DOMNode $objectNode, string $mappingFile): ObjectMappingInterface
133
    {
134
        /** @var DOMNamedNodeMap $attributes */
135 1
        $objectNodeAttributes = $objectNode->attributes;
136
137 1
        $className = (string)$objectNodeAttributes->getNamedItem("class")->nodeValue;
138
139
        /** @var CallDefinitionInterface|null $factory */
140 1
        $factory = null;
141
142
        /** @var CallDefinitionInterface|null $factory */
143 1
        $serializer = null;
144
145
        /** @var DOMXPath $xpath */
146 1
        $xpath = $this->createXPath($objectNode);
147
148 1
        foreach ($xpath->query('rdm:factory', $objectNode) as $factoryNode) {
149
            /** @var DOMNode $factoryNode */
150
151
            /** @var array<MappingInterface> $argumentMappings */
152 1
            $argumentMappings = $this->readFieldMappings($factoryNode, $mappingFile);
153
154
            /** @var string $routineName */
155 1
            $routineName = (string)$factoryNode->attributes->getNamedItem('method')->nodeValue;
156
157
            /** @var string $objectReference */
158 1
            $objectReference = (string)$factoryNode->attributes->getNamedItem('object')->nodeValue;
159
160 1
            $factory = new CallDefinition($routineName, $objectReference, $argumentMappings);
161
        }
162
163 1
        if ($objectNodeAttributes->getNamedItem("factory") !== null && is_null($factory)) {
164 1
            $factory = $this->readCallDefinition(
165 1
                (string)$objectNodeAttributes->getNamedItem("factory")->nodeValue
166
            );
167
        }
168
169 1
        if ($objectNodeAttributes->getNamedItem("serialize") !== null) {
170 1
            $serializer = $this->readCallDefinition(
171 1
                (string)$objectNodeAttributes->getNamedItem("serialize")->nodeValue
172
            );
173
        }
174
175
        /** @var array<MappingInterface> $fieldMappings */
176 1
        $fieldMappings = $this->readFieldMappings($objectNode, $mappingFile);
177
178 1
        return new ObjectMapping(
179 1
            $className,
180 1
            $fieldMappings,
181 1
            sprintf(
182 1
                "in file '%s'",
183 1
                $mappingFile
184
            ),
185 1
            $factory,
186 1
            $serializer
187
        );
188
    }
189
190 1
    private function readCallDefinition(string $callDefinition): CallDefinitionInterface
191
    {
192
        /** @var string $routineName */
193 1
        $routineName = $callDefinition;
194
195
        /** @var string|null $objectReference */
196 1
        $objectReference = null;
197
198 1
        $callDefinition = str_replace('->', '::', $callDefinition);
199
200 1
        if (strpos($callDefinition, '::') !== false) {
201 1
            [$objectReference, $routineName] = explode('::', $callDefinition);
202
        }
203
204 1
        return new CallDefinition($routineName, $objectReference);
205
    }
206
207 1
    private function readChoice(
208
        DOMNode $choiceNode,
209
        string $mappingFile,
210
        string $defaultColumnName
211
    ): ChoiceMappingInterface {
212
        /** @var string|Column $columnName */
213 1
        $column = $defaultColumnName;
214
215 1
        if (!is_null($choiceNode->attributes->getNamedItem("column"))) {
216 1
            $column = (string)$choiceNode->attributes->getNamedItem("column")->nodeValue;
217
        }
218
219
        /** @var array<MappingInterface> $choiceMappings */
220 1
        $choiceMappings = array();
221
222
        /** @var DOMXPath $xpath */
223 1
        $xpath = $this->createXPath($choiceNode);
224
225 1
        foreach ($xpath->query('rdm:option', $choiceNode) as $optionNode) {
226
            /** @var DOMNode $optionNode */
227
228
            /** @var string $determinator */
229 1
            $determinator = (string)$optionNode->attributes->getNamedItem("name")->nodeValue;
230
231
            /** @var string $optionDefaultColumnName */
232 1
            $optionDefaultColumnName = sprintf("%s_%s", $defaultColumnName, $determinator);
233
234 1
            foreach ($this->readFieldMappings($optionNode, $mappingFile, $optionDefaultColumnName) as $mapping) {
235
                /** @var MappingInterface $mapping */
236
237 1
                $choiceMappings[$determinator] = $mapping;
238
            }
239
        }
240
241 1
        foreach ($xpath->query('orm:field', $choiceNode) as $fieldNode) {
242
            /** @var DOMNode $fieldNode */
243
244 1
            $column = $this->readDoctrineField($fieldNode);
245
        }
246
247 1
        return new ChoiceMapping($column, $choiceMappings, sprintf(
248 1
            "in file '%s'",
249 1
            $mappingFile
250
        ));
251
    }
252
253
    /**
254
     * @return array<MappingInterface>
255
     */
256 1
    private function readFieldMappings(
257
        DOMNode $objectNode,
258
        string $mappingFile,
259
        string $choiceDefaultColumnName = null
260
    ): array {
261
        /** @var DOMXPath $xpath */
262 1
        $xpath = $this->createXPath($objectNode);
263
264
        /** @var array<MappingInterface> $fieldMappings */
265 1
        $fieldMappings = array();
266
267 1
        foreach ($xpath->query('rdm:service', $objectNode) as $serviceNode) {
268
            /** @var DOMNode $serviceNode */
269
270 1
            $serviceMapping = $this->readService($serviceNode, $mappingFile);
271
272 1
            if (!is_null($serviceNode->attributes->getNamedItem("field"))) {
273
                /** @var string $fieldName */
274
                $fieldName = (string)$serviceNode->attributes->getNamedItem("field")->nodeValue;
275
276
                $fieldMappings[$fieldName] = $serviceMapping;
277
278
            } else {
279 1
                $fieldMappings[] = $serviceMapping;
280
            }
281
        }
282
283 1
        foreach ($xpath->query('rdm:choice', $objectNode) as $choiceNode) {
284
            /** @var DOMNode $choiceNode */
285
286
            /** @var string $defaultColumnName */
287 1
            $defaultColumnName = "";
288
289 1
            if (!is_null($choiceDefaultColumnName)) {
290
                $defaultColumnName = $choiceDefaultColumnName;
291
292 1
            } elseif (!is_null($choiceNode->attributes->getNamedItem("field"))) {
293
                $defaultColumnName = (string)$choiceNode->attributes->getNamedItem("field")->nodeValue;
294
            }
295
296 1
            $choiceMapping = $this->readChoice($choiceNode, $mappingFile, $defaultColumnName);
297
298 1
            if (!is_null($choiceNode->attributes->getNamedItem("field"))) {
299
                /** @var string $fieldName */
300
                $fieldName = (string)$choiceNode->attributes->getNamedItem("field")->nodeValue;
301
302
                $fieldMappings[$fieldName] = $choiceMapping;
303
304
            } else {
305 1
                $fieldMappings[] = $choiceMapping;
306
            }
307
        }
308
309 1
        foreach ($xpath->query('rdm:object', $objectNode) as $objectNode) {
310
            /** @var DOMNode $objectNode */
311
312
            /** @var ObjectMappingInterface $objectMapping */
313 1
            $objectMapping = $this->readObject($objectNode, $mappingFile);
314
315 1
            if (!is_null($objectNode->attributes->getNamedItem("field"))) {
316
                /** @var string $fieldName */
317
                $fieldName = (string)$objectNode->attributes->getNamedItem("field")->nodeValue;
318
319
                $fieldMappings[$fieldName] = $objectMapping;
320
321
            } else {
322 1
                $fieldMappings[] = $objectMapping;
323
            }
324
        }
325
326 1
        foreach ($xpath->query('orm:field', $objectNode) as $fieldNode) {
327
            /** @var DOMNode $fieldNode */
328
329
            /** @var Column $column */
330 1
            $column = $this->readDoctrineField($fieldNode);
331
332 1
            $fieldName = $column->getName();
333
334 1
            $fieldMappings[$fieldName] = new FieldMapping(
335 1
                $column,
336 1
                sprintf("in file '%s'", $mappingFile)
337
            );
338
        }
339
340 1
        foreach ($xpath->query('rdm:array', $objectNode) as $arrayNode) {
341
            /** @var DOMNode $arrayNode */
342
343
            /** @var ArrayMappingInterface $arrayMapping */
344 1
            $arrayMapping = $this->readArray($arrayNode, $mappingFile);
345
346 1
            if (!is_null($arrayNode->attributes->getNamedItem("field"))) {
347
                /** @var string $fieldName */
348 1
                $fieldName = (string)$arrayNode->attributes->getNamedItem("field")->nodeValue;
349
350 1
                $fieldMappings[$fieldName] = $arrayMapping;
351
352
            } else {
353 1
                $fieldMappings[] = $arrayMapping;
354
            }
355
        }
356
357 1
        return $fieldMappings;
358
    }
359
360 1
    private function readService(DOMNode $serviceNode, string $mappingFile): ServiceMappingInterface
361
    {
362
        /** @var bool $lax */
363 1
        $lax = false;
364
365 1
        if ($serviceNode->attributes->getNamedItem("lax") instanceof DOMNode) {
366 1
            $lax = strtolower($serviceNode->attributes->getNamedItem("lax")->nodeValue) === 'true';
367
        }
368
369
        /** @var string $serviceId */
370 1
        $serviceId = (string)$serviceNode->attributes->getNamedItem("id")->nodeValue;
371
372 1
        return new ServiceMapping($serviceId, $lax, sprintf(
373 1
            "in file '%s'",
374 1
            $mappingFile
375
        ));
376
    }
377
378 1
    private function readArray(DOMNode $arrayNode, string $mappingFile): ArrayMappingInterface
379
    {
380
        /** @var array<MappingInterface> $entryMappings */
381 1
        $entryMappings = $this->readFieldMappings($arrayNode, $mappingFile);
382
383
        /** @var DOMXPath $xpath */
384 1
        $xpath = $this->createXPath($arrayNode);
385
386 1
        foreach ($xpath->query('rdm:entry', $arrayNode) as $entryNode) {
387
            /** @var DOMNode $entryNode */
388
389
            /** @var string|null $key */
390 1
            $key = null;
391
392 1
            if ($entryNode->attributes->getNamedItem("key") instanceof DOMNode) {
393 1
                $key = (string)$entryNode->attributes->getNamedItem("key")->nodeValue;
394
            }
395
396 1
            foreach ($this->readFieldMappings($entryNode, $mappingFile) as $entryMapping) {
397
                /** @var MappingInterface $entryMapping */
398
399 1
                if (is_null($key)) {
400
                    $entryMappings[] = $entryMapping;
401
402
                } else {
403 1
                    $entryMappings[$key] = $entryMapping;
404
                }
405
406 1
                break;
407
            }
408
        }
409
410 1
        return new ArrayMapping($entryMappings, sprintf(
411 1
            "in file '%s'",
412 1
            $mappingFile
413
        ));
414
    }
415
416 1
    private function readDoctrineField(DOMNode $fieldNode): Column
417
    {
418
        /** @var array<string> $attributes */
419 1
        $attributes = array();
420
421
        /** @var array<string> $keyMap */
422
        $keyMap = array(
423 1
            'name'              => 'name',
424
            'type'              => 'type',
425
            'nullable'          => 'notnull',
426
            'length'            => 'length',
427
            'precision'         => 'precision',
428
            'scale'             => 'scale',
429
            'column-definition' => 'columnDefinition',
430
        );
431
432
        /** @var string $columnName */
433 1
        $columnName = null;
434
435
        /** @var Type $type */
436 1
        $type = null;
437
438 1
        foreach ($fieldNode->attributes as $key => $attribute) {
439
            /** @var DOMAttr $attribute */
440
441 1
            $attributeValue = (string)$attribute->nodeValue;
442
443 1
            if ($key === 'name') {
444 1
                $columnName = $attributeValue;
445
446 1
            } elseif ($key === 'type') {
447 1
                $type = Type::getType($attributeValue);
448
449 1
            } elseif (isset($keyMap[$key])) {
450 1
                if ($key === 'nullable') {
451
                    # target is 'notnull', so falue is reversed
452 1
                    $attributeValue = ($attributeValue === 'false');
453
                }
454
455 1
                $attributes[$keyMap[$key]] = $attributeValue;
456
            }
457
        }
458
459 1
        $column = new Column(
460 1
            $columnName,
461 1
            $type,
462 1
            $attributes
463
        );
464
465 1
        return $column;
466
    }
467
468
}
469