Completed
Push — master ( d8a83b...76c960 )
by Eric
02:56
created

XmlClassMetadataLoader   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 170
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 1
Bugs 1 Features 0
Metric Value
wmc 26
lcom 1
cbo 1
dl 0
loc 170
rs 10
c 1
b 1
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A loadFile() 0 11 2
B loadClass() 0 22 4
F loadProperty() 0 38 9
B loadDocument() 0 35 5
B validateDocument() 0 23 5
A setState() 0 6 1
1
<?php
2
3
/*
4
 * This file is part of the Ivory Serializer package.
5
 *
6
 * (c) Eric GELOEN <[email protected]>
7
 *
8
 * For the full copyright and license information, please read the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Ivory\Serializer\Mapping\Loader;
13
14
/**
15
 * @author GeLo <[email protected]>
16
 */
17
class XmlClassMetadataLoader extends AbstractFileClassMetadataLoader
18
{
19
    /**
20
     * {@inheritdoc}
21
     */
22
    protected function loadFile($file)
23
    {
24
        $data = [];
25
        $xml = simplexml_import_dom($this->loadDocument($file));
26
27
        foreach ($xml->class as $class) {
28
            $data[(string) $class['name']] = $this->loadClass($class);
29
        }
30
31
        return $data;
32
    }
33
34
    /**
35
     * @param \SimpleXMLElement $class
36
     *
37
     * @return mixed[]
38
     */
39
    private function loadClass(\SimpleXMLElement $class)
40
    {
41
        $definition = [];
42
43
        if (isset($class['exclusion-policy'])) {
44
            $definition['exclusion_policy'] = (string) $class['exclusion-policy'];
45
        }
46
47
        if (isset($class['order'])) {
48
            $definition['order'] = (string) $class['order'];
49
        }
50
51
        $properties = [];
52
53
        foreach ($class->property as $property) {
54
            $properties[(string) $property['name']] = $this->loadProperty($property);
55
        }
56
57
        $definition['properties'] = $properties;
58
59
        return $definition;
60
    }
61
62
    /**
63
     * @param \SimpleXMLElement $element
64
     *
65
     * @return mixed[]
66
     */
67
    private function loadProperty(\SimpleXMLElement $element)
68
    {
69
        $property = [];
70
71
        if (isset($element['alias'])) {
72
            $property['alias'] = (string) $element['alias'];
73
        }
74
75
        if (isset($element['type'])) {
76
            $property['type'] = (string) $element['type'];
77
        }
78
79
        if (isset($element['exclude'])) {
80
            $property['exclude'] = (string) $element['exclude'] === 'true';
81
        }
82
83
        if (isset($element['expose'])) {
84
            $property['expose'] = (string) $element['expose'] === 'true';
85
        }
86
87
        if (isset($element['since'])) {
88
            $property['since'] = (string) $element['since'];
89
        }
90
91
        if (isset($element['until'])) {
92
            $property['until'] = (string) $element['until'];
93
        }
94
95
        if (isset($element['max-depth'])) {
96
            $property['max_depth'] = (string) $element['max-depth'];
97
        }
98
99
        foreach ($element->group as $group) {
100
            $property['groups'][] = (string) $group;
101
        }
102
103
        return $property;
104
    }
105
106
    /**
107
     * @param string $file
108
     *
109
     * @return \DOMDocument
110
     */
111
    private function loadDocument($file)
112
    {
113
        $data = trim(file_get_contents($file));
114
115
        if (empty($data)) {
116
            throw new \InvalidArgumentException();
117
        }
118
119
        $internalErrors = libxml_use_internal_errors();
120
        $disableEntities = libxml_disable_entity_loader();
121
122
        $this->setState(true, true);
123
124
        $document = new \DOMDocument();
125
        $document->validateOnParse = true;
126
127
        if (!$document->loadXML($data, LIBXML_NONET | LIBXML_COMPACT)) {
128
            $this->setState($internalErrors, $disableEntities);
129
130
            throw new \InvalidArgumentException();
131
        }
132
133
        $document->normalizeDocument();
134
135
        foreach ($document->childNodes as $child) {
136
            if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) {
137
                throw new \InvalidArgumentException('The document type is not allowed.');
138
            }
139
        }
140
141
        $this->validateDocument($document, $internalErrors, $disableEntities);
142
        $this->setState($internalErrors, $disableEntities);
143
144
        return $document;
145
    }
146
147
    /**
148
     * @param \DOMDocument $document
149
     * @param bool         $internalErrors
150
     * @param bool         $disableEntities
151
     */
152
    private function validateDocument(\DOMDocument $document, $internalErrors, $disableEntities)
153
    {
154
        if (@$document->schemaValidateSource(file_get_contents(__DIR__.'/../Resource/mapping.xsd'))) {
155
            return;
156
        }
157
158
        $errors = [];
159
160
        foreach (libxml_get_errors() as $error) {
161
            $errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)',
162
                LIBXML_ERR_WARNING === $error->level ? 'WARNING' : 'ERROR',
163
                $error->code,
164
                trim($error->message),
165
                $error->file ?: 'n/a',
166
                $error->line,
167
                $error->column
168
            );
169
        }
170
171
        $this->setState($internalErrors, $disableEntities);
172
173
        throw new \InvalidArgumentException(implode(PHP_EOL, $errors));
174
    }
175
176
    /**
177
     * @param bool $internalErrors
178
     * @param bool $disableEntities
179
     */
180
    private function setState($internalErrors, $disableEntities)
181
    {
182
        libxml_use_internal_errors($internalErrors);
183
        libxml_disable_entity_loader($disableEntities);
184
        libxml_clear_errors();
185
    }
186
}
187