GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — master (#140)
by
unknown
56s
created

ArrayToXml   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 235
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 0
Metric Value
wmc 55
lcom 1
cbo 0
dl 0
loc 235
rs 6
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 26 4
A setNumericTagNamePrefix() 0 4 1
A convert() 0 19 1
A toXml() 0 4 1
A toDom() 0 4 1
A ensureValidDomProperties() 0 8 3
A setDomProperties() 0 10 2
A prettify() 0 7 1
D convertElement() 0 40 18
A addNumericNode() 0 6 2
A addNode() 0 10 2
A addCollectionNode() 0 12 3
A addSequentialNode() 0 12 3
A isArrayAllKeySequential() 0 16 4
A addAttributes() 0 6 2
B createRootElement() 0 22 6
A removeControlCharacters() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like ArrayToXml 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 ArrayToXml, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Spatie\ArrayToXml;
4
5
use DOMDocument;
6
use DOMElement;
7
use DOMException;
8
use Exception;
9
10
class ArrayToXml
11
{
12
    protected $document;
13
14
    protected $replaceSpacesByUnderScoresInKeyNames = true;
15
16
    protected $numericTagNamePrefix = 'numeric_';
17
18
    public function __construct(
19
        array $array,
20
        $rootElement = '',
21
        $replaceSpacesByUnderScoresInKeyNames = true,
22
        $xmlEncoding = null,
23
        $xmlVersion = '1.0',
24
        $domProperties = []
25
    ) {
26
        $this->document = new DOMDocument($xmlVersion, $xmlEncoding);
27
28
        if (! empty($domProperties)) {
29
            $this->setDomProperties($domProperties);
30
        }
31
32
        $this->replaceSpacesByUnderScoresInKeyNames = $replaceSpacesByUnderScoresInKeyNames;
33
34
        if ($this->isArrayAllKeySequential($array) && ! empty($array)) {
35
            throw new DOMException('Invalid Character Error');
36
        }
37
38
        $root = $this->createRootElement($rootElement);
39
40
        $this->document->appendChild($root);
41
42
        $this->convertElement($root, $array);
43
    }
44
45
    public function setNumericTagNamePrefix(string $prefix)
46
    {
47
        $this->numericTagNamePrefix = $prefix;
48
    }
49
50
    public static function convert(
51
        array $array,
52
        $rootElement = '',
53
        bool $replaceSpacesByUnderScoresInKeyNames = true,
54
        string $xmlEncoding = null,
55
        string $xmlVersion = '1.0',
56
        array $domProperties = []
57
    ) {
58
        $converter = new static(
59
            $array,
60
            $rootElement,
61
            $replaceSpacesByUnderScoresInKeyNames,
62
            $xmlEncoding,
63
            $xmlVersion,
64
            $domProperties
65
        );
66
67
        return $converter->toXml();
68
    }
69
70
    public function toXml(): string
71
    {
72
        return $this->document->saveXML();
73
    }
74
75
    public function toDom(): DOMDocument
76
    {
77
        return $this->document;
78
    }
79
80
    protected function ensureValidDomProperties(array $domProperties)
81
    {
82
        foreach ($domProperties as $key => $value) {
83
            if (! property_exists($this->document, $key)) {
84
                throw new Exception($key.' is not a valid property of DOMDocument');
85
            }
86
        }
87
    }
88
89
    public function setDomProperties(array $domProperties)
90
    {
91
        $this->ensureValidDomProperties($domProperties);
92
93
        foreach ($domProperties as $key => $value) {
94
            $this->document->{$key} = $value;
95
        }
96
97
        return $this;
98
    }
99
100
    public function prettify()
101
    {
102
        $this->document->preserveWhiteSpace = false;
103
        $this->document->formatOutput = true;
104
105
        return $this;
106
    }
107
108
    private function convertElement(DOMElement $element, $value)
109
    {
110
        $sequential = $this->isArrayAllKeySequential($value);
111
112
        if (! is_array($value)) {
113
            $value = htmlspecialchars($value);
114
115
            $value = $this->removeControlCharacters($value);
116
117
            $element->nodeValue = $value;
118
119
            return;
120
        }
121
122
        foreach ($value as $key => $data) {
123
            if (! $sequential) {
124
                if (($key === '_attributes') || ($key === '@attributes')) {
125
                    $this->addAttributes($element, $data);
126
                } elseif ((($key === '_value') || ($key === '@value')) && is_string($data)) {
127
                    $element->nodeValue = htmlspecialchars($data);
128
                } elseif ((($key === '_cdata') || ($key === '@cdata')) && is_string($data)) {
129
                    $element->appendChild($this->document->createCDATASection($data));
130
                } elseif ((($key === '_mixed') || ($key === '@mixed')) && is_string($data)) {
131
                    $fragment = $this->document->createDocumentFragment();
132
                    $fragment->appendXML($data);
133
                    $element->appendChild($fragment);
134
                } elseif ($key === '__numeric') {
135
                    $this->addNumericNode($element, $data);
136
                } elseif (substr( $key, 0, 9 ) === "__custom:") {
137
                    $this->addNode($element, explode(':', $key)[1], $data);
138
                } else {
139
                    $this->addNode($element, $key, $data);
140
                }
141
            } elseif (is_array($data)) {
142
                $this->addCollectionNode($element, $data);
143
            } else {
144
                $this->addSequentialNode($element, $data);
145
            }
146
        }
147
    }
148
149
    protected function addNumericNode(DOMElement $element, $value)
150
    {
151
        foreach ($value as $key => $item) {
152
            $this->convertElement($element, [$this->numericTagNamePrefix.$key => $item]);
153
        }
154
    }
155
156
    protected function addNode(DOMElement $element, $key, $value)
157
    {
158
        if ($this->replaceSpacesByUnderScoresInKeyNames) {
159
            $key = str_replace(' ', '_', $key);
160
        }
161
162
        $child = $this->document->createElement($key);
163
        $element->appendChild($child);
164
        $this->convertElement($child, $value);
165
    }
166
167
    protected function addCollectionNode(DOMElement $element, $value)
168
    {
169
        if ($element->childNodes->length === 0 && $element->attributes->length === 0) {
0 ignored issues
show
Bug introduced by
The property length does not seem to exist in DOMNamedNodeMap.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
170
            $this->convertElement($element, $value);
171
172
            return;
173
        }
174
175
        $child = $this->document->createElement($element->tagName);
176
        $element->parentNode->appendChild($child);
177
        $this->convertElement($child, $value);
178
    }
179
180
    protected function addSequentialNode(DOMElement $element, $value)
181
    {
182
        if (empty($element->nodeValue) && ! is_numeric($element->nodeValue)) {
183
            $element->nodeValue = htmlspecialchars($value);
184
185
            return;
186
        }
187
188
        $child = new DOMElement($element->tagName);
189
        $child->nodeValue = htmlspecialchars($value);
190
        $element->parentNode->appendChild($child);
191
    }
192
193
    protected function isArrayAllKeySequential($value)
194
    {
195
        if (! is_array($value)) {
196
            return false;
197
        }
198
199
        if (count($value) <= 0) {
200
            return true;
201
        }
202
203
        if (\key($value) === '__numeric') {
204
            return false;
205
        }
206
207
        return array_unique(array_map('is_int', array_keys($value))) === [true];
208
    }
209
210
    protected function addAttributes(DOMElement $element, array $data)
211
    {
212
        foreach ($data as $attrKey => $attrVal) {
213
            $element->setAttribute($attrKey, $attrVal);
214
        }
215
    }
216
217
    protected function createRootElement($rootElement): DOMElement
218
    {
219
        if (is_string($rootElement)) {
220
            $rootElementName = $rootElement ?: 'root';
221
222
            return $this->document->createElement($rootElementName);
223
        }
224
225
        $rootElementName = $rootElement['rootElementName'] ?? 'root';
226
227
        $element = $this->document->createElement($rootElementName);
228
229
        foreach ($rootElement as $key => $value) {
230
            if ($key !== '_attributes' && $key !== '@attributes') {
231
                continue;
232
            }
233
234
            $this->addAttributes($element, $rootElement[$key]);
235
        }
236
237
        return $element;
238
    }
239
240
    protected function removeControlCharacters(string $value): string
241
    {
242
        return preg_replace('/[\x00-\x09\x0B\x0C\x0E-\x1F\x7F]/', '', $value);
243
    }
244
}
245