Completed
Push — master ( 528f7d...f4b721 )
by Kacper
04:19
created

XmlElement   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 276
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 0%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
dl 0
loc 276
rs 8.4864
c 2
b 0
f 0
ccs 0
cts 102
cp 0
wmc 48
lcom 1
cbo 3

24 Methods

Rating   Name   Duplication   Size   Complexity  
A __toString() 0 4 1
C xml() 0 29 8
A setAttribute() 0 13 3
A getAttribute() 0 8 2
A getNamespaces() 0 12 3
A attributes() 0 12 2
A lookupUri() 0 4 2
A lookupPrefix() 0 4 1
A setNamespace() 0 8 2
A getNamespace() 0 4 1
A getChildren() 0 4 1
A append() 0 15 4
A getName() 0 4 2
A getPrefix() 0 4 1
A getLocalName() 0 4 1
A setParent() 0 7 2
A elements() 0 9 2
A element() 0 4 1
A all() 0 4 1
A query() 0 4 1
A init() 0 1 1
A plain() 0 18 2
A _prefix() 0 8 2
A resolve() 0 10 2

How to fix   Complexity   

Complex Class

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

1
<?php
2
/**
3
 * XMPP Library
4
 *
5
 * Copyright (C) 2016, Some right reserved.
6
 *
7
 * @author Kacper "Kadet" Donat <[email protected]>
8
 *
9
 * Contact with author:
10
 * Xmpp: [email protected]
11
 * E-mail: [email protected]
12
 *
13
 * From Kadet with love.
14
 */
15
16
namespace Kadet\Xmpp\Xml;
17
18
use Kadet\Xmpp\Exception\InvalidArgumentException;
19
use Kadet\Xmpp\Utils\Accessors;
20
21
use \Kadet\Xmpp\Utils\helper;
22
use \Kadet\Xmpp\Utils\filter;
23
24
/**
25
 * Class XmlElement
26
 * @package Kadet\Xmpp\Xml
27
 *
28
 * @property string $localName Tag name without prefix.
29
 * @property string $namespace
30
 * @property string $prefix
31
 * @property string $name
32
 * @property XmlElement $parent
33
 * @property XmlElement[] $children
34
 */
35
class XmlElement
36
{
37
    use Accessors;
38
39
    public static $tidy = [
40
        'indent'           => true,
41
        'input-xml'        => true,
42
        'output-xml'       => true,
43
        'drop-empty-paras' => false,
44
        'wrap'             => 0
45
    ];
46
47
    private $_localName;
48
    private $_prefix = null;
49
50
    private $_namespaces = [
51
    ];
52
    private $_attributes = [];
53
54
    /**
55
     * @var XmlElement
56
     */
57
    private $_parent;
58
59
    /**
60
     * @var XmlElement[]
61
     */
62
    private $_children = [];
63
64
    public function __toString()
65
    {
66
        return trim($this->xml(true));
67
    }
68
69
    public function xml($clean = true): string
70
    {
71
        if($this->namespace && $this->_prefix === null) {
72
            $this->_prefix = $this->lookupPrefix($this->namespace);
73
        }
74
75
        $attributes = $this->attributes();
76
77
        $result = "<{$this->name}";
78
        $result .= ' '.implode(' ', array_map(function($key, $value) {
79
            return $key.'="'.htmlspecialchars($value, ENT_QUOTES).'"';
80
        }, array_keys($attributes), array_values($attributes)));
81
82
        if(!empty($this->_children)) {
83
            $result .= ">".implode('', array_map(function($element) {
84
                if(is_string($element)) {
85
                    return htmlspecialchars($element);
86
                } elseif ($element instanceof XmlElement) {
87
                    return $element->xml(false);
88
                }
89
90
                return (string)$element;
91
            }, $this->_children))."</{$this->name}>";
92
        } else {
93
            $result .= "/>";
94
        }
95
96
        return $clean && function_exists('tidy_repair_string') ? tidy_repair_string($result, self::$tidy) : $result;
97
    }
98
99
    public function setAttribute(string $attribute, $value, string $uri = null)
100
    {
101
        if($uri === 'http://www.w3.org/2000/xmlns/') {
102
            $this->_namespaces[(string)$value] = $attribute;
103
            return;
104
        }
105
106
        if($uri !== null) {
107
            $attribute = $this->_prefix($attribute, $uri);
108
        }
109
110
        $this->_attributes[$attribute] = $value;
111
    }
112
113
    public function getAttribute(string $attribute, string $uri = null)
114
    {
115
        if($uri !== null) {
116
            $attribute = $this->_prefix($attribute, $uri);
117
        }
118
119
        return $this->_attributes[$attribute] ?? false;
120
    }
121
122
    /**
123
     * @return array
124
     */
125
    public function getNamespaces($parent = true): array
126
    {
127
        if(!$this->_parent) {
128
            return $this->_namespaces;
129
        }
130
131
        if($parent) {
132
            return array_merge($this->_namespaces, $this->_parent->getNamespaces());
133
        } else {
134
            return array_diff_assoc($this->_namespaces, $this->_parent->getNamespaces());
135
        }
136
    }
137
138
    public function attributes(): array
139
    {
140
        $namespaces = $this->getNamespaces(false);
141
        $namespaces = array_map(function($prefix, $uri) {
142
            return [ $prefix ? "xmlns:{$prefix}" : 'xmlns', $uri ];
143
        }, array_values($namespaces), array_keys($namespaces));
144
145
        return array_merge(
146
            $this->_attributes,
147
            array_combine(array_column($namespaces, 0), array_column($namespaces, 1))
148
        );
149
    }
150
151
    public function lookupUri(string $prefix = null)
152
    {
153
        return array_search($prefix, $this->getNamespaces()) ?: false;
154
    }
155
156
    public function lookupPrefix(string $uri = null)
157
    {
158
        return $this->getNamespaces()[$uri] ?? false;
159
    }
160
161
    public function setNamespace(string $uri, $prefix = false)
162
    {
163
        if($prefix === false) {
164
            $prefix = $this->_prefix;
165
        }
166
167
        $this->_namespaces[$uri] = $prefix;
168
    }
169
170
    public function getNamespace()
171
    {
172
        return $this->lookupUri($this->prefix);
173
    }
174
175
    public function getChildren()
176
    {
177
        return $this->_children;
178
    }
179
180
    public function append($element)
181
    {
182
        if(!is_string($element) && !$element instanceof XmlElement) {
183
            throw new InvalidArgumentException(helper\format('$element should be either string or object of {class} class, {type} given', [
184
                'class' => XmlElement::class,
185
                'type'  => helper\typeof($element)
186
            ]));
187
        }
188
189
        if($element instanceof XmlElement) {
190
            $element->parent  = $this;
191
        }
192
193
        $this->_children[] = $element;
194
    }
195
196
    public function getName()
197
    {
198
        return ($this->_prefix ? $this->prefix.':' : null).$this->localName;
199
    }
200
201
    public function getPrefix()
202
    {
203
        return $this->_prefix;
204
    }
205
206
    public function getLocalName()
207
    {
208
        return $this->_localName;
209
    }
210
211
    protected function setParent(XmlElement $parent)
212
    {
213
        $this->_parent = $parent;
214
        if($this->namespace === false) {
215
            $this->namespace = $parent->namespace;
216
        }
217
    }
218
219
    /**
220
     * Retrieves array of matching elements
221
     *
222
     * @param string $name  Requested element tag name
223
     * @param null   $uri   Requested element namespace
224
     *
225
     * @return XmlElement[] Found Elements
226
     */
227
    public function elements($name, $uri = null) : array
228
    {
229
        $predicate = filter\tag($name);
230
        if($uri !== null) {
231
            $predicate = filter\all($predicate, filter\xmlns($uri));
232
        }
233
234
        return $this->all($predicate);
235
    }
236
237
    /**
238
     * Returns one element at specified index (for default the first one).
239
     *
240
     * @param string $name  Requested element tag name
241
     * @param string $uri   Requested element namespace
242
     * @param int    $index Index of element to retrieve
243
     *
244
     * @return XmlElement|false Retrieved element
245
     */
246
    public function element(string $name, string $uri = null, int $index = 0)
247
    {
248
        return $this->elements($name, $uri)[$index] ?? false;
0 ignored issues
show
Bug introduced by
It seems like $uri defined by parameter $uri on line 246 can also be of type string; however, Kadet\Xmpp\Xml\XmlElement::elements() does only seem to accept null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
249
    }
250
251
    public function all($predicate) {
252
        $predicate = filter\predicate($predicate);
253
        return array_filter($this->_children, $predicate);
254
    }
255
256
    /**
257
     * @param string|null $query
258
     * @return XPathQuery
259
     */
260
    public function query(string $query = null)
261
    {
262
        return new XPathQuery($query, $this);
263
    }
264
265
    protected function init() { }
266
267
    public static function plain(string $name, string $uri = null)
268
    {
269
        $reflection = new \ReflectionClass(static::class);
270
271
        list($name, $prefix) = static::resolve($name);
272
273
        /** @var XmlElement $element */
274
        $element = $reflection->newInstanceWithoutConstructor();
275
        $element->_localName = $name;
276
        $element->_prefix    = $prefix;
277
278
        if ($uri !== null) {
279
            $element->namespace = $uri;
280
        }
281
282
        $element->init();
283
        return $element;
284
    }
285
286
    /**
287
     * @param string $name
288
     * @param string $uri
289
     * @return string
290
     */
291
    protected function _prefix(string $name, string $uri): string
292
    {
293
        if (($prefix = $this->lookupPrefix($uri)) === false) {
294
            throw new InvalidArgumentException(helper\format('URI "{uri}" is not a registered namespace', ['uri' => $uri]));
295
        }
296
297
        return "{$prefix}:{$name}";
298
    }
299
300
    public static function resolve($name)
301
    {
302
        $prefix = null;
303
        if (($pos = strpos($name, ':')) !== false) {
304
            $prefix = substr($name, 0, $pos);
305
            $name   = substr($name, $pos + 1);
306
        }
307
308
        return [$name, $prefix];
309
    }
310
}
311