Completed
Push — master ( d6fe8f...a5bda6 )
by Бабичев
76:33 queued 73:52
created

XMLReader   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 390
Duplicated Lines 0 %

Test Coverage

Coverage 88.24%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 390
ccs 30
cts 34
cp 0.8824
rs 8.3673
c 1
b 0
f 0
wmc 45

18 Methods

Rating   Name   Duplication   Size   Complexity  
A document() 0 10 2
A element() 0 3 1
A _asArray() 0 10 2
A _simpleXml() 0 13 3
A asArray() 0 6 2
B _property() 0 23 5
A _asData() 0 15 3
B _pushArray() 0 26 4
A _convertStorage() 0 8 2
A asObject() 0 3 1
A addCollectionNode() 0 16 2
A addAttributes() 0 5 2
B convert() 0 26 5
A asXML() 0 16 2
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);
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
     * @param array              $attributes
227
     *
228
     * @return string
229
     */
230 1
    public function asXML($storage, $name = 'bavix', array $attributes = [])
231
    {
232 1
        $element = $this->element($name);
233
234 1
        foreach ($attributes as $attr => $value) {
235
            $element->setAttribute($attr, $value);
236
        }
237
238 1
        $this->addAttributes($element, $this->copyright);
239 1
        $this->document()->appendChild($element);
240 1
        $this->convert($element, $this->_convertStorage($storage));
241 1
        $xml = $this->document()->saveXML();
242
243 1
        $this->document = null;
244
245 1
        return $xml;
246
    }
247
248
    /**
249
     * @param DOMElement $element
250
     * @param mixed      $storage
251
     *
252
     * @throws Exceptions\Blank
253
     *
254
     * @codeCoverageIgnore
255
     */
256
    protected function convert(DOMElement $element, $storage)
257
    {
258
        if (!is_array($storage))
259
        {
260
            $element->nodeValue = htmlspecialchars($storage);
261
262
            return;
263
        }
264
265
        if (empty($storage))
266
        {
267
            throw new Exceptions\Blank('Array is empty');
268
        }
269
270
        $isInt      = Arr::map(Arr::getKeys($storage), '\is_int');
271
        $sequential = !Arr::in($isInt, false);
272
273
        foreach ($storage as $key => $data)
274
        {
275
            if ($sequential)
276
            {
277
                $this->sequential($element, $data);
278
                continue;
279
            }
280
281
            $this->addNodeWithKey($key, $element, $data);
282
        }
283
    }
284
285
    /**
286
     * @param string     $key
287
     * @param DOMElement $element
288
     * @param mixed      $storage
289
     *
290
     * @codeCoverageIgnore
291
     */
292
    protected function addNodeWithKey($key, DOMElement $element, $storage)
293
    {
294
        if ($key === '@attributes')
295
        {
296
            $this->addAttributes($element, $storage);
297
        }
298
        else if ($key === '@value' && \is_string($storage))
299
        {
300
            $element->nodeValue = \htmlspecialchars($storage);
301
        }
302
        else
303
        {
304
            $this->addNode($element, $key, $storage);
305
        }
306
    }
307
308
    /**
309
     * @param DOMElement $element
310
     * @param mixed      $storage
311
     *
312
     * @codeCoverageIgnore
313
     */
314
    protected function sequential(DOMElement $element, $storage)
315
    {
316
        if (is_array($storage))
317
        {
318
            $this->addCollectionNode($element, $storage);
319
320
            return;
321
        }
322
323
        $this->addSequentialNode($element, $storage);
324
    }
325
326
    /**
327
     * @param DOMElement $element
328
     * @param string     $key
329
     * @param mixed      $value
330
     *
331
     * @throws Exceptions\Blank
332
     *
333
     * @codeCoverageIgnore
334
     */
335
    protected function addNode(DOMElement $element, $key, $value)
336
    {
337
        $key   = \str_replace(' ', '-', $key);
338
        $child = $this->document()->createElement($key);
339
        $element->appendChild($child);
340
        $this->convert($child, $value);
341
    }
342
343
    /**
344
     * @param DOMElement $element
345
     * @param mixed      $value
346
     *
347
     * @throws Exceptions\Blank
348
     *
349
     * @codeCoverageIgnore
350
     */
351
    protected function addCollectionNode(DOMElement $element, $value)
352
    {
353
        if ($element->childNodes->length === 0)
354
        {
355
            $this->convert($element, $value);
356
357
            return;
358
        }
359
360
        /**
361
         * @var $child DOMElement
362
         */
363
        $child = $this->document()->createElement($element->nodeName);
364
//        $child = $element->cloneNode();
0 ignored issues
show
Unused Code Comprehensibility introduced by
55% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
365
        $element->parentNode->appendChild($child);
366
        $this->convert($child, $value);
367
    }
368
369
    /**
370
     * @param DOMElement $element
371
     * @param mixed      $value
372
     *
373
     * @codeCoverageIgnore
374
     */
375
    protected function addSequentialNode(DOMElement $element, $value)
376
    {
377
        if (empty($element->nodeValue))
378
        {
379
            $element->nodeValue = \htmlspecialchars($value);
380
381
            return;
382
        }
383
384
        $child = $this->document()->createElement($element->nodeName);
385
//        $child = $element->cloneNode();
0 ignored issues
show
Unused Code Comprehensibility introduced by
55% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
386
        $child->nodeValue = \htmlspecialchars($value);
387
        $element->parentNode->appendChild($child);
388
    }
389
390
    /**
391
     * @param DOMElement         $element
392
     * @param array|\Traversable $storage
393
     *
394
     * @codeCoverageIgnore
395
     */
396
    protected function addAttributes(DOMElement $element, $storage)
397
    {
398
        foreach ($storage as $attrKey => $attrVal)
399
        {
400
            $element->setAttribute($attrKey, $attrVal);
401
        }
402
    }
403
404
}
405