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.

ArrayToXml   C
last analyzed

Complexity

Total Complexity 57

Size/Duplication

Total Lines 248
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 0
Metric Value
wmc 57
lcom 1
cbo 0
dl 0
loc 248
rs 5.04
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
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 toDom() 0 4 1
A ensureValidDomProperties() 0 8 3
A setDomProperties() 0 10 2
A __construct() 0 26 4
A setNumericTagNamePrefix() 0 4 1
A convert() 0 19 1
A toXml() 0 8 2
A prettify() 0 7 1
A dropXmlDeclaration() 0 6 1
D convertElement() 0 40 18
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 $addXmlDeclaration = true;
17
18
    protected $numericTagNamePrefix = 'numeric_';
19
20
    public function __construct(
21
        array $array,
22
        $rootElement = '',
23
        $replaceSpacesByUnderScoresInKeyNames = true,
24
        $xmlEncoding = null,
25
        $xmlVersion = '1.0',
26
        $domProperties = []
27
    ) {
28
        $this->document = new DOMDocument($xmlVersion, $xmlEncoding);
29
30
        if (! empty($domProperties)) {
31
            $this->setDomProperties($domProperties);
32
        }
33
34
        $this->replaceSpacesByUnderScoresInKeyNames = $replaceSpacesByUnderScoresInKeyNames;
35
36
        if ($this->isArrayAllKeySequential($array) && ! empty($array)) {
37
            throw new DOMException('Invalid Character Error');
38
        }
39
40
        $root = $this->createRootElement($rootElement);
41
42
        $this->document->appendChild($root);
43
44
        $this->convertElement($root, $array);
45
    }
46
47
    public function setNumericTagNamePrefix(string $prefix)
48
    {
49
        $this->numericTagNamePrefix = $prefix;
50
    }
51
52
    public static function convert(
53
        array $array,
54
        $rootElement = '',
55
        bool $replaceSpacesByUnderScoresInKeyNames = true,
56
        string $xmlEncoding = null,
57
        string $xmlVersion = '1.0',
58
        array $domProperties = []
59
    ) {
60
        $converter = new static(
61
            $array,
62
            $rootElement,
63
            $replaceSpacesByUnderScoresInKeyNames,
64
            $xmlEncoding,
65
            $xmlVersion,
66
            $domProperties
67
        );
68
69
        return $converter->toXml();
70
    }
71
72
    public function toXml(): string
73
    {
74
        if ($this->addXmlDeclaration === false) {
75
            return $this->document->saveXml($this->document->documentElement);
76
        }
77
78
        return $this->document->saveXML();
79
    }
80
81
    public function toDom(): DOMDocument
82
    {
83
        return $this->document;
84
    }
85
86
    protected function ensureValidDomProperties(array $domProperties)
87
    {
88
        foreach ($domProperties as $key => $value) {
89
            if (! property_exists($this->document, $key)) {
90
                throw new Exception($key.' is not a valid property of DOMDocument');
91
            }
92
        }
93
    }
94
95
    public function setDomProperties(array $domProperties)
96
    {
97
        $this->ensureValidDomProperties($domProperties);
98
99
        foreach ($domProperties as $key => $value) {
100
            $this->document->{$key} = $value;
101
        }
102
103
        return $this;
104
    }
105
106
    public function prettify()
107
    {
108
        $this->document->preserveWhiteSpace = false;
109
        $this->document->formatOutput = true;
110
111
        return $this;
112
    }
113
114
    public function dropXmlDeclaration()
115
    {
116
        $this->addXmlDeclaration = false;
117
118
        return $this;
119
    }
120
121
    private function convertElement(DOMElement $element, $value)
122
    {
123
        $sequential = $this->isArrayAllKeySequential($value);
124
125
        if (! is_array($value)) {
126
            $value = htmlspecialchars($value);
127
128
            $value = $this->removeControlCharacters($value);
129
130
            $element->nodeValue = $value;
131
132
            return;
133
        }
134
135
        foreach ($value as $key => $data) {
136
            if (! $sequential) {
137
                if (($key === '_attributes') || ($key === '@attributes')) {
138
                    $this->addAttributes($element, $data);
139
                } elseif ((($key === '_value') || ($key === '@value')) && is_string($data)) {
140
                    $element->nodeValue = htmlspecialchars($data);
141
                } elseif ((($key === '_cdata') || ($key === '@cdata')) && is_string($data)) {
142
                    $element->appendChild($this->document->createCDATASection($data));
143
                } elseif ((($key === '_mixed') || ($key === '@mixed')) && is_string($data)) {
144
                    $fragment = $this->document->createDocumentFragment();
145
                    $fragment->appendXML($data);
146
                    $element->appendChild($fragment);
147
                } elseif ($key === '__numeric') {
148
                    $this->addNumericNode($element, $data);
149
                } elseif (substr($key, 0, 9) === '__custom:') {
150
                    $this->addNode($element, explode(':', $key)[1], $data);
151
                } else {
152
                    $this->addNode($element, $key, $data);
153
                }
154
            } elseif (is_array($data)) {
155
                $this->addCollectionNode($element, $data);
156
            } else {
157
                $this->addSequentialNode($element, $data);
158
            }
159
        }
160
    }
161
162
    protected function addNumericNode(DOMElement $element, $value)
163
    {
164
        foreach ($value as $key => $item) {
165
            $this->convertElement($element, [$this->numericTagNamePrefix.$key => $item]);
166
        }
167
    }
168
169
    protected function addNode(DOMElement $element, $key, $value)
170
    {
171
        if ($this->replaceSpacesByUnderScoresInKeyNames) {
172
            $key = str_replace(' ', '_', $key);
173
        }
174
175
        $child = $this->document->createElement($key);
176
        $element->appendChild($child);
177
        $this->convertElement($child, $value);
178
    }
179
180
    protected function addCollectionNode(DOMElement $element, $value)
181
    {
182
        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...
183
            $this->convertElement($element, $value);
184
185
            return;
186
        }
187
188
        $child = $this->document->createElement($element->tagName);
189
        $element->parentNode->appendChild($child);
190
        $this->convertElement($child, $value);
191
    }
192
193
    protected function addSequentialNode(DOMElement $element, $value)
194
    {
195
        if (empty($element->nodeValue) && ! is_numeric($element->nodeValue)) {
196
            $element->nodeValue = htmlspecialchars($value);
197
198
            return;
199
        }
200
201
        $child = new DOMElement($element->tagName);
202
        $child->nodeValue = htmlspecialchars($value);
203
        $element->parentNode->appendChild($child);
204
    }
205
206
    protected function isArrayAllKeySequential($value)
207
    {
208
        if (! is_array($value)) {
209
            return false;
210
        }
211
212
        if (count($value) <= 0) {
213
            return true;
214
        }
215
216
        if (\key($value) === '__numeric') {
217
            return false;
218
        }
219
220
        return array_unique(array_map('is_int', array_keys($value))) === [true];
221
    }
222
223
    protected function addAttributes(DOMElement $element, array $data)
224
    {
225
        foreach ($data as $attrKey => $attrVal) {
226
            $element->setAttribute($attrKey, $attrVal);
227
        }
228
    }
229
230
    protected function createRootElement($rootElement): DOMElement
231
    {
232
        if (is_string($rootElement)) {
233
            $rootElementName = $rootElement ?: 'root';
234
235
            return $this->document->createElement($rootElementName);
236
        }
237
238
        $rootElementName = $rootElement['rootElementName'] ?? 'root';
239
240
        $element = $this->document->createElement($rootElementName);
241
242
        foreach ($rootElement as $key => $value) {
243
            if ($key !== '_attributes' && $key !== '@attributes') {
244
                continue;
245
            }
246
247
            $this->addAttributes($element, $rootElement[$key]);
248
        }
249
250
        return $element;
251
    }
252
253
    protected function removeControlCharacters(string $value): string
254
    {
255
        return preg_replace('/[\x00-\x09\x0B\x0C\x0E-\x1F\x7F]/', '', $value);
256
    }
257
}
258