Completed
Push — master ( 4a4bf4...63fe9e )
by Gerrit
02:30
created

MappingXmlDriver   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 206
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 95.12%

Importance

Changes 0
Metric Value
wmc 26
lcom 1
cbo 6
dl 0
loc 206
ccs 78
cts 82
cp 0.9512
rs 10
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
B loadRDMMetadataForClass() 0 50 6
C readChoice() 0 59 11
A readService() 0 17 2
B readDoctrineField() 0 51 6
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
27
final class MappingXmlDriver implements MappingDriverInterface
28
{
29
30
    const RDM_SCHEMA_URI = "http://github.com/addiks/symfony_rdm/tree/master/Resources/mapping-schema.v1.xsd";
31
    const DOCTRINE_SCHEMA_URI = "http://doctrine-project.org/schemas/orm/doctrine-mapping";
32
33
    /**
34
     * @var FileLocator
35
     */
36
    private $fileLocator;
37
38
    /**
39
     * @var string
40
     */
41
    private $schemaFilePath;
42
43 2
    public function __construct(
44
        FileLocator $fileLocator,
45
        string $schemaFilePath
46
    ) {
47 2
        $this->fileLocator = $fileLocator;
48 2
        $this->schemaFilePath = $schemaFilePath;
49 2
    }
50
51 1
    public function loadRDMMetadataForClass(string $className): ?EntityMappingInterface
52
    {
53
        /** @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...
54 1
        $mapping = null;
55
56
        /** @var array<MappingInterface> $fieldMappings */
57 1
        $fieldMappings = array();
58
59 1
        if ($this->fileLocator->fileExists($className)) {
60
            /** @var string $mappingFile */
61 1
            $mappingFile = $this->fileLocator->findMappingFile($className);
62
63 1
            $dom = new DOMDocument();
64 1
            $dom->loadXML(file_get_contents($mappingFile));
65
66
            /** @var string $rdmPrefix */
67 1
            $rdmPrefix = $dom->lookupPrefix(self::RDM_SCHEMA_URI);
68
69 1
            if (!empty($rdmPrefix)) {
70 1
                $xpath = new DOMXPath($dom);
71
72 1
                $xpath->registerNamespace($rdmPrefix, self::RDM_SCHEMA_URI);
73 1
                $xpath->registerNamespace("d", self::DOCTRINE_SCHEMA_URI);
74
75 1
                foreach ($xpath->query("//d:entity/{$rdmPrefix}:service", $dom) as $serviceNode) {
76
                    /** @var DOMNode $serviceNode */
77
78
                    /** @var string $fieldName */
79 1
                    $fieldName = (string)$serviceNode->attributes->getNamedItem("field")->nodeValue;
80
81 1
                    $fieldMappings[$fieldName] = $this->readService($serviceNode, $mappingFile);
82
                }
83
84 1
                foreach ($xpath->query("//d:entity/{$rdmPrefix}:choice", $dom) as $choiceNode) {
85
                    /** @var DOMNode $choiceNode */
86
87
                    /** @var string $fieldName */
88 1
                    $fieldName = (string)$choiceNode->attributes->getNamedItem("field")->nodeValue;
89
90 1
                    $fieldMappings[$fieldName] = $this->readChoice($choiceNode, $mappingFile, $fieldName);
91
                }
92
            }
93
        }
94
95 1
        if (!empty($fieldMappings)) {
96 1
            $mapping = new EntityMapping($className, $fieldMappings);
97
        }
98
99 1
        return $mapping;
100
    }
101
102 1
    private function readChoice(DOMNode $choiceNode, string $mappingFile, string $defaultColumnName): ChoiceMapping
103
    {
104
        /** @var string|Colum $columnName */
105 1
        $column = $defaultColumnName;
106
107 1
        if (!is_null($choiceNode->attributes->getNamedItem("column"))) {
108 1
            $column = (string)$choiceNode->attributes->getNamedItem("column")->nodeValue;
109
        }
110
111
        /** @var array<MappingInterface> $choiceMappings */
112 1
        $choiceMappings = array();
113
114 1
        foreach ($choiceNode->childNodes as $optionNode) {
115
            /** @var DOMNode $optionNode */
116
117 1
            while ($optionNode instanceof DOMNode && in_array($optionNode->nodeType, [
118 1
                XML_TEXT_NODE,
119 1
                XML_COMMENT_NODE
120
            ])) {
121 1
                $optionNode = $optionNode->nextSibling;
122
            }
123
124 1
            if ($optionNode instanceof DOMNode) {
125
                /** @var mixed $nodeName */
126 1
                $nodeName = $optionNode->namespaceURI . ":" . $optionNode->localName;
127
128 1
                if ($nodeName === self::RDM_SCHEMA_URI . ":option") {
129
                    /** @var string $determinator */
130 1
                    $determinator = (string)$optionNode->attributes->getNamedItem("name")->nodeValue;
131
132
                    /** @var DOMNode $optionMappingNode */
133 1
                    $optionMappingNode = $optionNode->firstChild;
134
135 1
                    while (in_array($optionMappingNode->nodeType, [XML_TEXT_NODE, XML_COMMENT_NODE])) {
136 1
                        $optionMappingNode = $optionMappingNode->nextSibling;
137
                    }
138
139 1
                    if ($optionMappingNode->nodeName === $optionMappingNode->prefix . ":service") {
140 1
                        $choiceMappings[$determinator] = $this->readService($optionMappingNode, $mappingFile);
141
142
                    } elseif ($optionMappingNode->nodeName === $optionMappingNode->prefix . ":choice") {
143
                        $choiceMappings[$determinator] = $this->readChoice($optionMappingNode, $mappingFile, sprintf(
144
                            "%s_%s",
145
                            $defaultColumnName,
146 1
                            $determinator
147
                        ));
148
                    }
149
150 1
                } elseif ($nodeName === self::DOCTRINE_SCHEMA_URI . ":field") {
151 1
                    $column = $this->readDoctrineField($optionNode);
152
                }
153
            }
154
        }
155
156 1
        return new ChoiceMapping($column, $choiceMappings, sprintf(
157 1
            "in file '%s'",
158 1
            $mappingFile
159
        ));
160
    }
161
162 1
    private function readService(DOMNode $serviceNode, string $mappingFile): ServiceMapping
163
    {
164
        /** @var bool $lax */
165 1
        $lax = false;
166
167 1
        if ($serviceNode->attributes->getNamedItem("lax") instanceof DOMNode) {
168 1
            $lax = strtolower($serviceNode->attributes->getNamedItem("lax")->nodeValue) === 'true';
169
        }
170
171
        /** @var string $serviceId */
172 1
        $serviceId = (string)$serviceNode->attributes->getNamedItem("id")->nodeValue;
173
174 1
        return new ServiceMapping($serviceId, $lax, sprintf(
175 1
            "in file '%s'",
176 1
            $mappingFile
177
        ));
178
    }
179
180 1
    private function readDoctrineField(DOMNode $fieldNode): Column
181
    {
182
        /** @var array<string> $attributes */
183 1
        $attributes = array();
184
185
        /** @var array<string> $keyMap */
186
        $keyMap = array(
187 1
            'name'              => 'name',
188
            'type'              => 'type',
189
            'nullable'          => 'notnull',
190
            'length'            => 'length',
191
            'precision'         => 'precision',
192
            'scale'             => 'scale',
193
            'column-definition' => 'columnDefinition',
194
        );
195
196
        /** @var string $columnName */
197 1
        $columnName = null;
198
199
        /** @var Type $type */
200 1
        $type = null;
201
202 1
        foreach ($fieldNode->attributes as $key => $attribute) {
203
            /** @var DOMAttr $attribute */
204
205 1
            $attributeValue = (string)$attribute->nodeValue;
206
207 1
            if ($key === 'name') {
208 1
                $columnName = $attributeValue;
209
210 1
            } elseif ($key === 'type') {
211 1
                $type = Type::getType($attributeValue);
212
213 1
            } elseif (isset($keyMap[$key])) {
214 1
                if ($key === 'nullable') {
215
                    # target is 'notnull', so falue is reversed
216 1
                    $attributeValue = ($attributeValue === 'false');
217
                }
218
219 1
                $attributes[$keyMap[$key]] = $attributeValue;
220
            }
221
        }
222
223 1
        $column = new Column(
224 1
            $columnName,
225 1
            $type,
226 1
            $attributes
227
        );
228
229 1
        return $column;
230
    }
231
232
}
233