XMLStream   B
last analyzed

Complexity

Total Complexity 44

Size/Duplication

Total Lines 242
Duplicated Lines 4.13 %

Coupling/Cohesion

Components 3
Dependencies 4

Importance

Changes 0
Metric Value
dl 10
loc 242
rs 8.8798
c 0
b 0
f 0
wmc 44
lcom 3
cbo 4

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
A setDefaultNS() 0 4 1
A finish() 0 8 1
A __destroy() 0 7 2
A feed() 0 4 1
A finalize() 0 4 1
B startXML() 10 30 7
D endXML() 0 46 21
A charXML() 0 6 2
A getId() 0 5 1
A addIdHandler() 0 7 2
A addXPathHandler() 0 18 4

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

1
<?php
2
namespace PHPDaemon\XMLStream;
3
4
class XMLStream
5
{
6
    use \PHPDaemon\Traits\EventHandlers;
7
    use \PHPDaemon\Traits\ClassWatchdog;
8
    use \PHPDaemon\Traits\StaticObjectWatchdog;
9
10
    protected $parser;
11
    protected $xml_depth = 0;
12
    protected $current_ns = [];
13
    protected $idhandlers = [];
14
    protected $xpathhandlers = [];
15
    protected $default_ns;
16
17
    /**
18
     * Constructor
19
     * @return void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
20
     */
21
    public function __construct()
22
    {
23
        $this->parser = xml_parser_create('UTF-8');
24
        xml_parser_set_option($this->parser, XML_OPTION_SKIP_WHITE, 1);
25
        xml_parser_set_option($this->parser, XML_OPTION_TARGET_ENCODING, 'UTF-8');
26
        xml_set_object($this->parser, $this);
27
        xml_set_element_handler($this->parser, 'startXML', 'endXML');
28
        xml_set_character_data_handler($this->parser, 'charXML');
29
    }
30
31
    /**
32
     * Set default namespace
33
     * @param string $ns
34
     * @return void
35
     */
36
    public function setDefaultNS($ns)
37
    {
38
        $this->default_ns = $ns;
39
    }
40
41
    /**
42
     * Finishes the stream
43
     * @return void
44
     */
45
    public function finish()
46
    {
47
        $this->xml_depth = 0;
48
        $this->current_ns = [];
49
        $this->idhandlers = [];
50
        $this->xpathhandlers = [];
51
        $this->eventHandlers = [];
52
    }
53
54
    /**
55
     * Destructor
56
     * @return void
57
     */
58
    public function __destroy()
59
    {
60
        if ($this->parser) {
61
            xml_parse($this->parser, '', true);
62
            xml_parser_free($this->parser);
63
        }
64
    }
65
66
    /**
67
     * Feed stream
68
     * @param string $buf
69
     * @return void
70
     */
71
    public function feed($buf)
72
    {
73
        xml_parse($this->parser, $buf, false);
74
    }
75
76
    /**
77
     * Finalize stream
78
     * @return void
79
     */
80
    public function finalize()
81
    {
82
        xml_parse($this->parser, '', true);
83
    }
84
85
    /**
86
     * XML start callback
87
     *
88
     * @see xml_set_element_handler
89
     * @param resource $parser
90
     * @param string $name
91
     * @param array $attr
92
     * @return void
93
     */
94
    public function startXML($parser, $name, $attr)
0 ignored issues
show
Unused Code introduced by
The parameter $parser is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
95
    {
96
        ++$this->xml_depth;
97
        if (array_key_exists('XMLNS', $attr)) {
98
            $this->current_ns[$this->xml_depth] = $attr['XMLNS'];
99
        } else {
100
            $this->current_ns[$this->xml_depth] = $this->current_ns[$this->xml_depth - 1];
101
            if (!$this->current_ns[$this->xml_depth]) {
102
                $this->current_ns[$this->xml_depth] = $this->default_ns;
103
            }
104
        }
105
        $ns = $this->current_ns[$this->xml_depth];
106
        foreach ($attr as $key => $value) {
107 View Code Duplication
            if (mb_orig_strpos($key, ':') !== false) {
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...
108
                $key = explode(':', $key);
109
                $key = $key[1];
110
                $this->ns_map[$key] = $value;
0 ignored issues
show
Bug introduced by
The property ns_map does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
111
            }
112
        }
113 View Code Duplication
        if (mb_orig_strpos($name, ':') !== false) {
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...
114
            $name = explode(':', $name);
115
            $ns = $this->ns_map[$name[0]];
116
            $name = $name[1];
117
        }
118
        $obj = new XMLStreamObject($name, $ns, $attr);
119
        if ($this->xml_depth > 1) {
120
            $this->xmlobj[$this->xml_depth - 1]->subs[] = $obj;
0 ignored issues
show
Bug introduced by
The property xmlobj does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
121
        }
122
        $this->xmlobj[$this->xml_depth] = $obj;
123
    }
124
125
    /**
126
     * XML end callback
127
     *
128
     * @see xml_set_element_handler
129
     *
130
     * @param resource $parser
131
     * @param string $name
132
     * @return void
133
     */
134
    public function endXML($parser, $name)
0 ignored issues
show
Unused Code introduced by
The parameter $parser is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $name is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
135
    {
136
        --$this->xml_depth;
137
        if ($this->xml_depth === 1) {
138
            foreach ($this->xpathhandlers as $handler) {
139
                if (is_array($this->xmlobj) && array_key_exists(2, $this->xmlobj)) {
140
                    $searchxml = $this->xmlobj[2];
141
                    $nstag = array_shift($handler[0]);
142
                    if (($nstag[0] === null or $searchxml->ns === $nstag[0]) and ($nstag[1] === "*" or $nstag[1] === $searchxml->name)) {
143
                        foreach ($handler[0] as $nstag) {
144
                            if ($searchxml !== null and $searchxml->hasSub($nstag[1], $ns = $nstag[0])) {
145
                                $searchxml = $searchxml->sub($nstag[1], $ns = $nstag[0]);
146
                            } else {
147
                                $searchxml = null;
148
                                break;
149
                            }
150
                        }
151
                        if ($searchxml !== null) {
152
                            if ($handler[2] === null) {
153
                                $handler[2] = $this;
154
                            }
155
                            $handler[1]($this->xmlobj[2]);
156
                        }
157
                    }
158
                }
159
            }
160
            foreach ($this->idhandlers as $id => $handler) {
161
                if (array_key_exists('id', $this->xmlobj[2]->attrs) and $this->xmlobj[2]->attrs['id'] == $id) {
162
                    $handler($this->xmlobj[2]);
163
                    #id handlers are only used once
164
                    unset($this->idhandlers[$id]);
165
                    break;
166
                }
167
            }
168
            if (is_array($this->xmlobj)) {
169
                $this->xmlobj = array_slice($this->xmlobj, 0, 1);
170
                if (isset($this->xmlobj[0]) && $this->xmlobj[0] instanceof XMLStreamObject) {
171
                    $this->xmlobj[0]->subs = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $subs.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
172
                }
173
            }
174
            unset($this->xmlobj[2]);
175
        }
176
        if ($this->xml_depth === 0) {
177
            $this->event('streamEnd');
178
        }
179
    }
180
181
    /**
182
     * XML character callback
183
     * @see xml_set_character_data_handler
184
     *
185
     * @param resource $parser
186
     * @param string $data
187
     */
188
    public function charXML($parser, $data)
0 ignored issues
show
Unused Code introduced by
The parameter $parser is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
189
    {
190
        if (array_key_exists($this->xml_depth, $this->xmlobj)) {
191
            $this->xmlobj[$this->xml_depth]->data .= $data;
192
        }
193
    }
194
195
    /**
196
     * Get next ID
197
     *
198
     * @return integer
199
     */
200
    public function getId()
201
    {
202
        $this->lastid++;
0 ignored issues
show
Bug introduced by
The property lastid does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
203
        return $this->lastid;
204
    }
205
206
    /**
207
     * Add ID Handler
208
     *
209
     * @param integer $id
210
     * @param callable $cb
211
     */
212
    public function addIdHandler($id, $cb)
213
    {
214
        if ($cb === null) {
215
            return;
216
        }
217
        $this->idhandlers[$id] = $cb;
218
    }
219
220
    /**
221
     * Add XPath Handler
222
     *
223
     * @param string $xpath
224
     * @param \Closure $cb
225
     * @param null $obj
226
     */
227
    public function addXPathHandler($xpath, $cb, $obj = null)
228
    {
229
        if (preg_match_all("/\(?{[^\}]+}\)?(\/?)[^\/]+/", $xpath, $regs)) {
230
            $ns_tags = $regs[0];
231
        } else {
232
            $ns_tags = [$xpath];
233
        }
234
        foreach ($ns_tags as $ns_tag) {
235
            list($l, $r) = explode("}", $ns_tag);
236
            if ($r !== null) {
237
                $xpart = [substr($l, 1), $r];
238
            } else {
239
                $xpart = [null, $l];
240
            }
241
            $xpath_array[] = $xpart;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$xpath_array was never initialized. Although not strictly required by PHP, it is generally a good practice to add $xpath_array = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
242
        }
243
        $this->xpathhandlers[] = [$xpath_array, $cb, $obj, $xpath];
0 ignored issues
show
Bug introduced by
The variable $xpath_array 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...
244
    }
245
}
246