Passed
Push — master ( 25ed05...407684 )
by Бабичев
05:15
created

XMLReader   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 374
Duplicated Lines 0 %

Test Coverage

Coverage 88.24%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 374
ccs 30
cts 34
cp 0.8824
rs 8.3157
c 1
b 0
f 0
wmc 43

17 Methods

Rating   Name   Duplication   Size   Complexity  
B _asArray() 0 33 5
A document() 0 10 2
B _property() 0 24 5
A _asData() 0 15 3
A element() 0 3 1
A addCollectionNode() 0 16 2
A _simpleXml() 0 13 3
A addAttributes() 0 5 2
A asArray() 0 7 2
B convert() 0 26 5
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
                    $properties :
72 1
                    $this->_asArray($properties);
73
74 1
                if (empty($output['@' . $property]))
75
                {
76
                    Arr::remove($output, '@' . $property);
77
                }
78
            }
79
80
        }
81
82 1
        return $output;
83
    }
84
85
    /**
86
     * @param \SimpleXMLElement $element
87
     *
88
     * @return array|string
89
     *
90
     * @codeCoverageIgnore
91
     */
92
    protected function _asData(\SimpleXMLElement $element)
93
    {
94
        $output = $this->_property($element, 'attributes');
95
96
        if (!$element->count())
97
        {
98
            $output['@value'] = (string)$element;
99
100
            if (!isset($output['@attributes']))
101
            {
102
                $output = $output['@value'];
103
            }
104
        }
105
106
        return $output;
107
    }
108
109
    /**
110
     * @param \SimpleXMLElement $element
111
     *
112
     * @return array|string
113
     *
114
     * @codeCoverageIgnore
115
     */
116
    protected function _asArray(\SimpleXMLElement $element)
117
    {
118
        $output = $this->_asData($element);
119
120
        if (!$element->count())
121
        {
122
            return $output;
123
        }
124
125
        $first = [];
126
127
        /**
128
         * @var \SimpleXMLElement $item
129
         */
130
        foreach ($element as $key => $item)
131
        {
132
            if (!isset($output[$key]))
133
            {
134
                $first[$key]  = true;
135
                $output[$key] = $this->_asArray($item);
136
                continue;
137
            }
138
139
            if (!empty($first[$key]))
140
            {
141
                $output[$key] = [$output[$key]];
142
            }
143
144
            $output[$key][] = $this->_asArray($item);
145
            $first[$key]    = false;
146
        }
147
148
        return $output;
149
    }
150
151
    /**
152
     * @param string|\DOMNode $mixed
153
     *
154
     * @return \SimpleXMLElement
155
     *
156
     * @codeCoverageIgnore
157
     */
158
    protected function _simpleXml($mixed)
159
    {
160
        if ($mixed instanceof \DOMNode)
161
        {
162
            return \simplexml_import_dom($mixed);
163
        }
164
165
        if (File::isFile($mixed))
166
        {
167
            return \simplexml_load_file($mixed);
168
        }
169
170
        return \simplexml_load_string($mixed);
171
    }
172
173
    /**
174
     * @param string|\DOMNode $mixed
175
     *
176
     * @return array
177
     */
178 1
    public function asArray($mixed)
179
    {
180 1
        $data = $this->_simpleXml($mixed);
181
182 1
        return $data ?
183 1
            $this->_asArray($data) :
184 1
            null;
185
    }
186
187
    /**
188
     * @return \DOMDocument
189
     */
190
    public function asObject()
191
    {
192
        return clone $this->document();
193
    }
194
195
    /**
196
     * @param array|\Traversable $storage
197
     *
198
     * @return array
199
     *
200
     * @codeCoverageIgnore
201
     */
202
    protected function _convertStorage($storage)
203
    {
204
        if ($storage instanceof \Traversable)
205
        {
206
            return \iterator_to_array($storage);
207
        }
208
209
        return $storage;
210
    }
211
212
    /**
213
     * @param array|\Traversable $storage
214
     * @param string             $name
215
     *
216
     * @return string
217
     */
218 1
    public function asXML($storage, $name = 'bavix')
219
    {
220 1
        $element = $this->element($name);
221
222 1
        $this->addAttributes($element, $this->copyright);
223 1
        $this->document()->appendChild($element);
224 1
        $this->convert($element, $this->_convertStorage($storage));
225 1
        $xml = $this->document()->saveXML();
226
227 1
        $this->document = null;
228
229 1
        return $xml;
230
    }
231
232
    /**
233
     * @param DOMElement $element
234
     * @param mixed      $storage
235
     *
236
     * @throws Exceptions\Blank
237
     *
238
     * @codeCoverageIgnore
239
     */
240
    protected function convert(DOMElement $element, $storage)
241
    {
242
        if (!is_array($storage))
243
        {
244
            $element->nodeValue = htmlspecialchars($storage);
245
246
            return;
247
        }
248
249
        if (empty($storage))
250
        {
251
            throw new Exceptions\Blank('Array is empty');
252
        }
253
254
        $isInt      = Arr::map(Arr::getKeys($storage), '\is_int');
255
        $sequential = !Arr::in($isInt, false);
256
257
        foreach ($storage as $key => $data)
258
        {
259
            if ($sequential)
260
            {
261
                $this->sequential($element, $data);
262
                continue;
263
            }
264
265
            $this->addNodeWithKey($key, $element, $data);
266
        }
267
    }
268
269
    /**
270
     * @param string     $key
271
     * @param DOMElement $element
272
     * @param mixed      $storage
273
     *
274
     * @codeCoverageIgnore
275
     */
276
    protected function addNodeWithKey($key, DOMElement $element, $storage)
277
    {
278
        if ($key === '@attributes')
279
        {
280
            $this->addAttributes($element, $storage);
281
        }
282
        else if ($key === '@value' && \is_string($storage))
283
        {
284
            $element->nodeValue = \htmlspecialchars($storage);
285
        }
286
        else
287
        {
288
            $this->addNode($element, $key, $storage);
289
        }
290
    }
291
292
    /**
293
     * @param DOMElement $element
294
     * @param mixed      $storage
295
     *
296
     * @codeCoverageIgnore
297
     */
298
    protected function sequential(DOMElement $element, $storage)
299
    {
300
        if (is_array($storage))
301
        {
302
            $this->addCollectionNode($element, $storage);
303
304
            return;
305
        }
306
307
        $this->addSequentialNode($element, $storage);
308
    }
309
310
    /**
311
     * @param DOMElement $element
312
     * @param string     $key
313
     * @param mixed      $value
314
     *
315
     * @throws Exceptions\Blank
316
     *
317
     * @codeCoverageIgnore
318
     */
319
    protected function addNode(DOMElement $element, $key, $value)
320
    {
321
        $key   = \str_replace(' ', '-', $key);
322
        $child = $this->document()->createElement($key);
323
        $element->appendChild($child);
324
        $this->convert($child, $value);
325
    }
326
327
    /**
328
     * @param DOMElement $element
329
     * @param mixed      $value
330
     *
331
     * @throws Exceptions\Blank
332
     *
333
     * @codeCoverageIgnore
334
     */
335
    protected function addCollectionNode(DOMElement $element, $value)
336
    {
337
        if ($element->childNodes->length === 0)
338
        {
339
            $this->convert($element, $value);
340
341
            return;
342
        }
343
344
        /**
345
         * @var $child DOMElement
346
         */
347
        $child = $this->document()->createElement($element->nodeName);
348
//        $child = $element->cloneNode();
349
        $element->parentNode->appendChild($child);
350
        $this->convert($child, $value);
351
    }
352
353
    /**
354
     * @param DOMElement $element
355
     * @param mixed      $value
356
     *
357
     * @codeCoverageIgnore
358
     */
359
    protected function addSequentialNode(DOMElement $element, $value)
360
    {
361
        if (empty($element->nodeValue))
362
        {
363
            $element->nodeValue = \htmlspecialchars($value);
364
365
            return;
366
        }
367
368
        $child = $this->document()->createElement($element->nodeName);
369
//        $child = $element->cloneNode();
370
        $child->nodeValue = \htmlspecialchars($value);
371
        $element->parentNode->appendChild($child);
372
    }
373
374
    /**
375
     * @param DOMElement         $element
376
     * @param array|\Traversable $storage
377
     *
378
     * @codeCoverageIgnore
379
     */
380
    protected function addAttributes(DOMElement $element, $storage)
381
    {
382
        foreach ($storage as $attrKey => $attrVal)
383
        {
384
            $element->setAttribute($attrKey, $attrVal);
385
        }
386
    }
387
388
}
389