XmlReaderParser   D
last analyzed

Complexity

Total Complexity 86

Size/Duplication

Total Lines 345
Duplicated Lines 15.36 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 86
lcom 1
cbo 4
dl 53
loc 345
ccs 0
cts 261
cp 0
rs 4.8717
c 0
b 0
f 0

2 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
D parse() 53 326 85

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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

1
<?php
2
3
namespace Fxmlrpc\Serialization\Parser;
4
5
use Fxmlrpc\Serialization\Exception\FaultException;
6
use Fxmlrpc\Serialization\Exception\UnexpectedTagException;
7
use Fxmlrpc\Serialization\Parser;
8
use Fxmlrpc\Serialization\Value\Base64Value;
9
use Fxmlrpc\Serialization\XmlChecker;
10
11
/**
12
 * Parser to parse XML responses into its PHP representation using XML Reader extension.
13
 *
14
 * @author Lars Strojny <[email protected]>
15
 */
16
final class XmlReaderParser implements Parser
17
{
18
    /**
19
     * @var bool
20
     */
21
    private $validateResponse;
22
23
    /**
24
     * @param bool $validateResponse
25
     */
26
    public function __construct($validateResponse = true)
27
    {
28
        $this->validateResponse = $validateResponse;
29
    }
30
31
    /**
32
     * {@inheritdoc}
33
     */
34
    public function parse($xmlString)
35
    {
36
        if ($this->validateResponse) {
37
            XmlChecker::isValid($xmlString);
38
        }
39
40
        $useErrors = libxml_use_internal_errors(true);
41
42
        $xml = new \XMLReader();
43
        $xml->xml($xmlString, 'UTF-8', LIBXML_COMPACT | LIBXML_NOCDATA | LIBXML_NOBLANKS | LIBXML_PARSEHUGE);
44
        $xml->setParserProperty(\XMLReader::VALIDATE, false);
45
        $xml->setParserProperty(\XMLReader::LOADDTD, false);
46
47
// This following assignments are auto-generated using Fxmlrpc\Serialization\CodeGenerator\XmlReaderParserBitmaskGenerator
48
// Don’t edit manually
49
        static $flagmethodResponse = 0b000000000000000000000000001;
50
        static $flagparams = 0b000000000000000000000000010;
51
        static $flagfault = 0b000000000000000000000000100;
52
        static $flagparam = 0b000000000000000000000001000;
53
        static $flagvalue = 0b000000000000000000000010000;
54
        static $flagarray = 0b000000000000000000000100000;
55
        static $flagmember = 0b000000000000000000001000000;
56
        static $flagname = 0b000000000000000000010000000;
57
        ${'flag#text'} = 0b000000000000000000100000000;
58
        static $flagstring = 0b000000000000000001000000000;
59
        static $flagstruct = 0b000000000000000010000000000;
60
        static $flagint = 0b000000000000000100000000000;
61
        static $flagbiginteger = 0b000000000000001000000000000;
62
        static $flagi8 = 0b000000000000010000000000000;
63
        static $flagi4 = 0b000000000000100000000000000;
64
        static $flagi2 = 0b000000000001000000000000000;
65
        static $flagi1 = 0b000000000010000000000000000;
66
        static $flagboolean = 0b000000000100000000000000000;
67
        static $flagdouble = 0b000000001000000000000000000;
68
        static $flagfloat = 0b000000010000000000000000000;
69
        static $flagbigdecimal = 0b000000100000000000000000000;
70
        ${'flagdateTime.iso8601'} = 0b000001000000000000000000000;
71
        static $flagdateTime = 0b000010000000000000000000000;
72
        static $flagbase64 = 0b000100000000000000000000000;
73
        static $flagnil = 0b001000000000000000000000000;
74
        static $flagdom = 0b010000000000000000000000000;
75
        static $flagdata = 0b100000000000000000000000000;
76
// End of auto-generated code
77
78
        $aggregates = [];
79
        $depth = 0;
80
        $nextExpectedElements = 0b000000000000000000000000001;
81
        $i = 0;
82
        $isFault = false;
83
84
        while ($xml->read()) {
85
            ++$i;
86
            $nodeType = $xml->nodeType;
87
88
            if (($nodeType === \XMLReader::COMMENT || $nodeType === \XMLReader::DOC_TYPE) ||
89
                (
90
                   $nodeType === \XMLReader::SIGNIFICANT_WHITESPACE &&
91
                   ($nextExpectedElements & 0b000000000000000000100000000) !== 0b000000000000000000100000000)
92
                ) {
93
                continue;
94
            }
95
96
            if ($nodeType === \XMLReader::ENTITY_REF) {
97
                return '';
98
            }
99
100
            $tagName = $xml->localName;
101
102
            if ($nextExpectedElements !== null &&
103
                ($flag = isset(${'flag'.$tagName}) ? ${'flag'.$tagName} : -1) &&
104
                ($nextExpectedElements & $flag) !== $flag
105
            ) {
106
                throw new UnexpectedTagException(
107
                    $tagName,
108
                    $nextExpectedElements,
109
                    get_defined_vars(),
110
                    $xml->depth,
111
                    $xml->readOuterXml()
112
                );
113
            }
114
115
            processing:
116
            switch ($nodeType) {
117
                case \XMLReader::ELEMENT:
118
                    switch ($tagName) {
119
                        case 'methodResponse':
120
                            // Next: params, fault
121
                            $nextExpectedElements = 0b000000000000000000000000110;
122
                            break;
123
124
                        case 'params':
125
                            // Next: param
126
                            $nextExpectedElements = 0b000000000000000000000001000;
127
                            $aggregates[$depth] = [];
128
                            break;
129
130
                        case 'fault':
131
                            $isFault = true;
132
                            // Break intentionally omitted
133
                        case 'param':
134
                            // Next: value
135
                            $nextExpectedElements = 0b000000000000000000000010000;
136
                            break;
137
138
                        case 'array':
139
                            $aggregates[++$depth] = [];
140
                            // Break intentionally omitted
141
                        case 'data':
142
                            // Next: array, data, value
143
                            $nextExpectedElements = 0b100000000000000000000110000;
144
                            break;
145
146
                        case 'struct':
147
                            // Next: struct, member, value
148
                            $nextExpectedElements = 0b000000000000000010001010000;
149
                            $aggregates[++$depth] = [];
150
                            break;
151
152
                        case 'member':
153
                            // Next: name, value
154
                            $nextExpectedElements = 0b000000000000000000010010000;
155
                            $aggregates[++$depth] = [];
156
                            break;
157
158
                        case 'name':
159
                            // Next: #text
160
                            $nextExpectedElements = 0b000000000000000000100000000;
161
                            $type = 'name';
162
                            break;
163
164
                        case 'value':
165
                            $nextExpectedElements = 0b011111111111111111100110000;
166
                            $type = 'value';
167
                            $aggregates[$depth + 1] = '';
168
                            break;
169
170
                        case 'base64':
171
                        case 'string':
172
                        case 'biginteger':
173
                        case 'i8':
174
                        case 'dateTime.iso8601':
175 View Code Duplication
                        case 'dateTime':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
176
                            // Next: value, $tagName, #text
177
                            $nextExpectedElements = 0b000000000000000000100010000 | ${'flag'.$tagName};
178
                            $type = $tagName;
179
                            $aggregates[$depth + 1] = '';
180
                            break;
181
182 View Code Duplication
                        case 'nil':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
183
                            // Next: value, $tagName
184
                            $nextExpectedElements = 0b001000000000000000000010000 | ${'flag'.$tagName};
185
                            $type = $tagName;
186
                            $aggregates[$depth + 1] = null;
187
                            break;
188
189
                        case 'int':
190
                        case 'i4':
191
                        case 'i2':
192 View Code Duplication
                        case 'i1':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
193
                            // Next: value, #text, $tagName
194
                            $nextExpectedElements = 0b000000000000000000100010000 | ${'flag'.$tagName};
195
                            $type = $tagName;
196
                            $aggregates[$depth + 1] = 0;
197
                            break;
198
199 View Code Duplication
                        case 'boolean':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
200
                            // Next: value, #text, $tagName
201
                            $nextExpectedElements = 0b000000000000000000100010000 | ${'flag'.$tagName};
202
                            $type = 'boolean';
203
                            $aggregates[$depth + 1] = false;
204
                            break;
205
206
                        case 'double':
207
                        case 'float':
208 View Code Duplication
                        case 'bigdecimal':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
209
                            // Next: value, #text, $tagName
210
                            $nextExpectedElements = 0b000000000000000000100010000 | ${'flag'.$tagName};
211
                            $type = $tagName;
212
                            $aggregates[$depth + 1] = 0.0;
213
                            break;
214
215
                        case 'dom':
216
                            $type = 'dom';
217
                            // Disable type checking
218
                            $nextExpectedElements = null;
219
                            $aggregates[$depth + 1] = $xml->readInnerXml();
220
                            break;
221
                    }
222
                    break;
223
224
                case \XMLReader::END_ELEMENT:
225
                    switch ($tagName) {
226
                        case 'params':
227
                        case 'fault':
228
                            break 3;
229
230
                        case 'param':
231
                            // Next: params, param
232
                            $nextExpectedElements = 0b000000000000000000000001010;
233
                            break;
234
235 View Code Duplication
                        case 'value':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
236
                            $nextExpectedElements = 0b100100000011100100011011100;
237
                            $aggregates[$depth][] = $aggregates[$depth + 1];
238
                            break;
239
240
                        case 'array':
241
                        case 'struct':
242
                            --$depth;
243
                            // Break intentionally omitted
244
                        case 'string':
245
                        case 'int':
246
                        case 'biginteger':
247
                        case 'i8':
248
                        case 'i4':
249
                        case 'i2':
250
                        case 'i1':
251
                        case 'boolean':
252
                        case 'double':
253
                        case 'float':
254
                        case 'bigdecimal':
255
                        case 'dateTime.iso8601':
256
                        case 'dateTime':
257
                        case 'base64':
258
                        case 'nil':
259
                            // Next: value
260
                            $nextExpectedElements = 0b000000000000000000000010000;
261
                            break;
262
263
                        case 'data':
264
                            // Next: array
265
                            $nextExpectedElements = 0b000000000000000000000100000;
266
                            break;
267
268 View Code Duplication
                        case 'name':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
269
                            // Next: value, member
270
                            $nextExpectedElements = 0b000000000000000000001010000;
271
                            $aggregates[$depth]['name'] = $aggregates[$depth + 1];
272
                            break;
273
274
                        case 'member':
275
                            // Next: struct, member
276
                            $nextExpectedElements = 0b000000000000000010001000000;
277
                            $aggregates[$depth - 1][$aggregates[$depth]['name']] = $aggregates[$depth][0];
278
                            unset($aggregates[$depth], $aggregates[$depth + 1]);
279
                            --$depth;
280
                            break;
281
                    }
282
                    break;
283
284
                case \XMLReader::TEXT:
285
                case \XMLReader::SIGNIFICANT_WHITESPACE:
286
                    switch ($type) {
0 ignored issues
show
Bug introduced by
The variable $type does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
287
                        case 'int':
288
                        case 'i4':
289
                        case 'i2':
290
                        case 'i1':
291
                            $value = (int) $xml->value;
292
                            break;
293
294
                        case 'boolean':
295
                            $value = $xml->value === '1';
296
                            break;
297
298
                        case 'double':
299
                        case 'float':
300
                        case 'bigdecimal':
301
                            $value = (float) $xml->value;
302
                            break;
303
304 View Code Duplication
                        case 'dateTime.iso8601':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
305
                            $value = \DateTime::createFromFormat(
306
                                'Ymd\TH:i:s',
307
                                $xml->value,
308
                                isset($timezone) ? $timezone : $timezone = new \DateTimeZone('UTC')
309
                            );
310
                            break;
311
312 View Code Duplication
                        case 'dateTime':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
313
                            $value = \DateTime::createFromFormat(
314
                                'Y-m-d\TH:i:s.uP',
315
                                $xml->value,
316
                                isset($timezone) ? $timezone : $timezone = new \DateTimeZone('UTC')
317
                            );
318
                            break;
319
320
                        case 'base64':
321
                            $value = Base64Value::deserialize($xml->value);
322
                            break;
323
324
                        case 'dom':
325
                            $doc = new \DOMDocument('1.0', 'UTF-8');
326
                            $doc->loadXML($aggregates[$depth + 1]);
327
                            $value = $doc;
328
                            break;
329
330
                        default:
331
                            $value = &$xml->value;
332
                            break;
333
                    }
334
335
                    $aggregates[$depth + 1] = $value;
336
                    if ($nextExpectedElements === null) {
337
                        break;
338
                    }
339
                    // Next: any
340
                    $nextExpectedElements = 0b111111111111111111111111111;
341
                    break;
342
            }
343
344
            if ($xml->isEmptyElement && $nodeType !== \XMLReader::END_ELEMENT) {
345
                $nodeType = \XMLReader::END_ELEMENT;
346
                goto processing;
347
            }
348
        }
349
350
        libxml_use_internal_errors($useErrors);
351
352
        $result = $aggregates ? array_pop($aggregates[0]) : null;
353
354
        if ($isFault) {
355
            throw FaultException::createFromResponse($result);
356
        }
357
358
        return $result;
359
    }
360
}
361