Passed
Push — master ( bf4e5d...8c6046 )
by Бабичев
02:33 queued 24s
created

XMLReader   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 385
Duplicated Lines 0 %

Test Coverage

Coverage 90.63%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 385
ccs 29
cts 32
cp 0.9063
rs 8.3396
c 1
b 0
f 0
wmc 44

18 Methods

Rating   Name   Duplication   Size   Complexity  
A document() 0 10 2
A element() 0 3 1
A addCollectionNode() 0 16 2
A _asArray() 0 10 2
A _simpleXml() 0 13 3
A addAttributes() 0 5 2
A asArray() 0 6 2
B _property() 0 23 5
A _asData() 0 15 3
B convert() 0 26 5
B _pushArray() 0 26 4
A asXML() 0 12 1
A _convertStorage() 0 8 2
A asObject() 0 3 1
A addSequentialNode() 0 13 2
A addNode() 0 6 1
A sequential() 0 10 2
A addNodeWithKey() 0 13 4

How to fix   Complexity   

Complex Class

Complex classes like XMLReader 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.

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 XMLReader, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Bavix\XMLReader;
4
5
use Bavix\Foundation\SharedInstance;
6
use Bavix\Helpers\File;
7
use Bavix\Helpers\Arr;
8
use Bavix\Exceptions;
9
use DOMElement;
10
11
class XMLReader
12
{
13
14
    use SharedInstance;
15
16
    /**
17
     * @var array
18
     */
19
    protected $copyright = [
20
        'created-with' => 'https://github.com/bavix/xml'
21
    ];
22
23
    /**
24
     * @var \DOMDocument
25
     */
26
    protected $document;
27
28
    /**
29
     * @return \DOMDocument
30
     */
31 1
    protected function document()
32
    {
33 1
        if (!$this->document)
34
        {
35 1
            $this->document                = new \DOMDocument('1.0', 'utf-8');
36 1
            $this->document->formatOutput  = true;
37 1
            $this->copyright['created-at'] = \date('Y-m-d H:i:s');
38
        }
39
40 1
        return $this->document;
41
    }
42
43
    /**
44
     * @param string $name
45
     *
46
     * @return \DOMElement
47
     */
48 1
    protected function element($name)
49
    {
50 1
        return $this->document()->createElement($name);
51
    }
52
53
    /**
54
     * @param \SimpleXMLElement $element
55
     * @param string            $property
56
     *
57
     * @return array
58
     */
59 1
    protected function _property(\SimpleXMLElement $element, $property)
60
    {
61 1
        $output = [];
62
63 1
        if (method_exists($element, $property))
64
        {
65
66 1
            $properties = $element->$property();
67
68 1
            if ($properties)
69
            {
70 1
                $output['@' . $property] = is_array($properties) ?
71 1
                    $properties : $this->_asArray($properties);
72
73 1
                if (empty($output['@' . $property]))
74
                {
75
                    Arr::remove($output, '@' . $property);
76
                }
77
            }
78
79
        }
80
81 1
        return $output;
82
    }
83
84
    /**
85
     * @param \SimpleXMLElement $element
86
     *
87
     * @return array|string
88
     *
89
     * @codeCoverageIgnore
90
     */
91
    protected function _asData(\SimpleXMLElement $element)
92
    {
93
        $output = $this->_property($element, 'attributes');
94
95
        if (!$element->count())
96
        {
97
            $output['@value'] = (string)$element;
98
99
            if (!isset($output['@attributes']))
100
            {
101
                $output = $output['@value'];
102
            }
103
        }
104
105
        return $output;
106
    }
107
108
    /**
109
     * @param \SimpleXMLElement $element
110
     *
111
     * @return array|string
112
     *
113
     * @codeCoverageIgnore
114
     */
115
    protected function _asArray(\SimpleXMLElement $element)
116
    {
117
        $output = $this->_asData($element);
118
119
        if (!$element->count())
120
        {
121
            return $output;
122
        }
123
124
        return $this->_pushArray($output, $element);
0 ignored issues
show
Bug introduced by
It seems like $output can also be of type string; however, parameter $output of Bavix\XMLReader\XMLReader::_pushArray() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

124
        return $this->_pushArray(/** @scrutinizer ignore-type */ $output, $element);
Loading history...
125
    }
126
127
    /**
128
     * @param array             $output
129
     * @param \SimpleXMLElement $element
130
     *
131
     * @return array
132
     *
133
     * @codeCoverageIgnore
134
     */
135
    protected function _pushArray(array &$output, \SimpleXMLElement $element)
136
    {
137
        $first = [];
138
139
        /**
140
         * @var \SimpleXMLElement $item
141
         */
142
        foreach ($element as $key => $item)
143
        {
144
            if (!isset($output[$key]))
145
            {
146
                $first[$key]  = true;
147
                $output[$key] = $this->_asArray($item);
148
                continue;
149
            }
150
151
            if (!empty($first[$key]))
152
            {
153
                $output[$key] = [$output[$key]];
154
            }
155
156
            $output[$key][] = $this->_asArray($item);
157
            $first[$key]    = false;
158
        }
159
160
        return $output;
161
    }
162
163
    /**
164
     * @param string|\DOMNode $mixed
165
     *
166
     * @return \SimpleXMLElement
167
     *
168
     * @codeCoverageIgnore
169
     */
170
    protected function _simpleXml($mixed)
171
    {
172
        if ($mixed instanceof \DOMNode)
173
        {
174
            return \simplexml_import_dom($mixed);
175
        }
176
177
        if (File::isFile($mixed))
178
        {
179
            return \simplexml_load_file($mixed);
180
        }
181
182
        return \simplexml_load_string($mixed);
183
    }
184
185
    /**
186
     * @param string|\DOMNode $mixed
187
     *
188
     * @return array
189
     */
190 1
    public function asArray($mixed)
191
    {
192 1
        $data = $this->_simpleXml($mixed);
193
194 1
        return $data ?
195 1
            $this->_asArray($data) : null;
196
    }
197
198
    /**
199
     * @return \DOMDocument
200
     */
201
    public function asObject()
202
    {
203
        return clone $this->document();
204
    }
205
206
    /**
207
     * @param array|\Traversable $storage
208
     *
209
     * @return array
210
     *
211
     * @codeCoverageIgnore
212
     */
213
    protected function _convertStorage($storage)
214
    {
215
        if ($storage instanceof \Traversable)
216
        {
217
            return \iterator_to_array($storage);
218
        }
219
220
        return $storage;
221
    }
222
223
    /**
224
     * @param array|\Traversable $storage
225
     * @param string             $name
226
     *
227
     * @return string
228
     */
229 1
    public function asXML($storage, $name = 'bavix')
230
    {
231 1
        $element = $this->element($name);
232
233 1
        $this->addAttributes($element, $this->copyright);
234 1
        $this->document()->appendChild($element);
235 1
        $this->convert($element, $this->_convertStorage($storage));
236 1
        $xml = $this->document()->saveXML();
237
238 1
        $this->document = null;
239
240 1
        return $xml;
241
    }
242
243
    /**
244
     * @param DOMElement $element
245
     * @param mixed      $storage
246
     *
247
     * @throws Exceptions\Blank
248
     *
249
     * @codeCoverageIgnore
250
     */
251
    protected function convert(DOMElement $element, $storage)
252
    {
253
        if (!is_array($storage))
254
        {
255
            $element->nodeValue = htmlspecialchars($storage);
256
257
            return;
258
        }
259
260
        if (empty($storage))
261
        {
262
            throw new Exceptions\Blank('Array is empty');
263
        }
264
265
        $isInt      = Arr::map(Arr::getKeys($storage), '\is_int');
266
        $sequential = !Arr::in($isInt, false);
267
268
        foreach ($storage as $key => $data)
269
        {
270
            if ($sequential)
271
            {
272
                $this->sequential($element, $data);
273
                continue;
274
            }
275
276
            $this->addNodeWithKey($key, $element, $data);
277
        }
278
    }
279
280
    /**
281
     * @param string     $key
282
     * @param DOMElement $element
283
     * @param mixed      $storage
284
     *
285
     * @codeCoverageIgnore
286
     */
287
    protected function addNodeWithKey($key, DOMElement $element, $storage)
288
    {
289
        if ($key === '@attributes')
290
        {
291
            $this->addAttributes($element, $storage);
292
        }
293
        else if ($key === '@value' && \is_string($storage))
294
        {
295
            $element->nodeValue = \htmlspecialchars($storage);
296
        }
297
        else
298
        {
299
            $this->addNode($element, $key, $storage);
300
        }
301
    }
302
303
    /**
304
     * @param DOMElement $element
305
     * @param mixed      $storage
306
     *
307
     * @codeCoverageIgnore
308
     */
309
    protected function sequential(DOMElement $element, $storage)
310
    {
311
        if (is_array($storage))
312
        {
313
            $this->addCollectionNode($element, $storage);
314
315
            return;
316
        }
317
318
        $this->addSequentialNode($element, $storage);
319
    }
320
321
    /**
322
     * @param DOMElement $element
323
     * @param string     $key
324
     * @param mixed      $value
325
     *
326
     * @throws Exceptions\Blank
327
     *
328
     * @codeCoverageIgnore
329
     */
330
    protected function addNode(DOMElement $element, $key, $value)
331
    {
332
        $key   = \str_replace(' ', '-', $key);
333
        $child = $this->document()->createElement($key);
334
        $element->appendChild($child);
335
        $this->convert($child, $value);
336
    }
337
338
    /**
339
     * @param DOMElement $element
340
     * @param mixed      $value
341
     *
342
     * @throws Exceptions\Blank
343
     *
344
     * @codeCoverageIgnore
345
     */
346
    protected function addCollectionNode(DOMElement $element, $value)
347
    {
348
        if ($element->childNodes->length === 0)
349
        {
350
            $this->convert($element, $value);
351
352
            return;
353
        }
354
355
        /**
356
         * @var $child DOMElement
357
         */
358
        $child = $this->document()->createElement($element->nodeName);
359
//        $child = $element->cloneNode();
360
        $element->parentNode->appendChild($child);
361
        $this->convert($child, $value);
362
    }
363
364
    /**
365
     * @param DOMElement $element
366
     * @param mixed      $value
367
     *
368
     * @codeCoverageIgnore
369
     */
370
    protected function addSequentialNode(DOMElement $element, $value)
371
    {
372
        if (empty($element->nodeValue))
373
        {
374
            $element->nodeValue = \htmlspecialchars($value);
375
376
            return;
377
        }
378
379
        $child = $this->document()->createElement($element->nodeName);
380
//        $child = $element->cloneNode();
381
        $child->nodeValue = \htmlspecialchars($value);
382
        $element->parentNode->appendChild($child);
383
    }
384
385
    /**
386
     * @param DOMElement         $element
387
     * @param array|\Traversable $storage
388
     *
389
     * @codeCoverageIgnore
390
     */
391
    protected function addAttributes(DOMElement $element, $storage)
392
    {
393
        foreach ($storage as $attrKey => $attrVal)
394
        {
395
            $element->setAttribute($attrKey, $attrVal);
396
        }
397
    }
398
399
}
400