Completed
Branch scrutinizer (fe02ae)
by Thomas
02:13
created

HtmlElement   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 367
Duplicated Lines 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
eloc 150
c 1
b 1
f 0
dl 0
loc 367
rs 8.4
wmc 50

15 Methods

Rating   Name   Duplication   Size   Complexity  
A addClass() 0 18 3
A hasAttr() 0 3 1
A getAttr() 0 3 1
A setCss() 0 30 6
A removeAttr() 0 3 1
A removeAllAttrs() 0 9 3
A removeClass() 0 23 4
A getCss() 0 19 4
A toggleClass() 0 30 5
B removeCss() 0 29 6
A setAttr() 0 3 1
A __construct() 0 3 1
A hasClass() 0 10 2
B setVal() 0 31 6
A getVal() 0 29 6

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
namespace Sulao\HtmlQuery;
4
5
use DOMElement;
6
7
/**
8
 * Class HtmlElement
9
 *
10
 * @package Sulao\HtmlQuery
11
 */
12
class HtmlElement extends HtmlNode
13
{
14
    /**
15
     * @var DOMElement
16
     */
17
    protected $node;
18
19
    /**
20
     * HtmlElement constructor.
21
     *
22
     * @param DOMElement $node
23
     */
24
    public function __construct(DOMElement $node)
25
    {
26
        $this->node = $node;
27
    }
28
29
    /**
30
     * Get the value of an attribute
31
     *
32
     * @param string $name
33
     *
34
     * @return string|null
35
     */
36
    public function getAttr(string $name)
37
    {
38
        return $this->node->getAttribute($name);
39
    }
40
41
    /**
42
     * Set attribute.
43
     *
44
     * @param string $name
45
     * @param string $value
46
     */
47
    public function setAttr(string $name, string $value)
48
    {
49
        $this->node->setAttribute($name, $value);
50
    }
51
52
    /**
53
     * Remove an attribute.
54
     *
55
     * @param string $attributeName
56
     */
57
    public function removeAttr(string $attributeName)
58
    {
59
        $this->node->removeAttribute($attributeName);
60
    }
61
62
    /**
63
     * Remove all attributes except the specified ones.
64
     *
65
     * @param string|array $except The attribute name(s) that won't be removed
66
     */
67
    public function removeAllAttrs($except = [])
68
    {
69
        $names = [];
70
        foreach (iterator_to_array($this->node->attributes) as $attribute) {
71
            $names[] = $attribute->name;
72
        }
73
74
        foreach (array_diff($names, (array) $except) as $name) {
75
            $this->node->removeAttribute($name);
76
        }
77
    }
78
79
    /**
80
     * Determine whether the node has the given attribute.
81
     *
82
     * @param string $attributeName
83
     *
84
     * @return bool
85
     */
86
    public function hasAttr(string $attributeName)
87
    {
88
        return $this->node->hasAttribute($attributeName);
89
    }
90
91
    /**
92
     * Adds the specified class(es).
93
     *
94
     * @param string $className
95
     */
96
    public function addClass(string $className)
97
    {
98
        if (!$this->hasAttr('class')) {
99
            $this->setAttr('class', $className);
100
            return;
101
        }
102
103
        $classNames = Helper::splitClass($className);
104
        $class = (string) $this->getAttr('class');
105
        $classes = Helper::splitClass($class);
106
107
        $classArr = array_diff($classNames, $classes);
108
        if (empty($classArr)) {
109
            return;
110
        }
111
112
        $class .= ' ' . implode(' ', $classArr);
113
        $this->setAttr('class', $class);
114
    }
115
116
    /**
117
     * Determine whether the node is assigned the given class.
118
     *
119
     * @param string $className
120
     *
121
     * @return bool
122
     */
123
    public function hasClass(string $className)
124
    {
125
        if (!$this->hasAttr('class')) {
126
            return false;
127
        }
128
129
        $class = (string) $this->getAttr('class');
130
        $classes = Helper::splitClass($class);
131
132
        return in_array($className, $classes);
133
    }
134
135
    /**
136
     * Remove a single class, multiple classes, or all classes.
137
     *
138
     * @param string|null $className
139
     */
140
    public function removeClass(?string $className = null)
141
    {
142
        if (!$this->hasAttr('class')) {
143
            return;
144
        }
145
146
        if (is_null($className)) {
147
            $this->removeAttr('class');
148
            return;
149
        }
150
151
        $classNames = Helper::splitClass($className);
152
        $class = (string) $this->getAttr('class');
153
        $classes = Helper::splitClass($class);
154
155
        $classArr = array_diff($classes, $classNames);
156
        if (empty($classArr)) {
157
            $this->removeAttr('class');
158
            return;
159
        }
160
161
        $class = implode(' ', $classArr);
162
        $this->setAttr('class', $class);
163
    }
164
165
    /**
166
     * Add or remove class(es), depending on either the class's presence
167
     * or the value of the state argument.
168
     *
169
     * @param string $className
170
     * @param bool|null   $state
171
     */
172
    public function toggleClass(string $className, ?bool $state = null)
173
    {
174
        if (!is_null($state)) {
175
            if ($state) {
176
                $this->addClass($className);
177
            } else {
178
                $this->removeClass($className);
179
            }
180
            return;
181
        }
182
183
        if (!$this->hasAttr('class')) {
184
            $this->setAttr('class', $className);
185
            return;
186
        }
187
188
        $classNames = Helper::splitClass($className);
189
        $classes = Helper::splitClass((string) $this->getAttr('class'));
190
191
        $classArr = array_diff($classes, $classNames);
192
        $classArr = array_merge(
193
            $classArr,
194
            array_diff($classNames, $classes)
195
        );
196
        if (empty($classArr)) {
197
            $this->removeClass($className);
198
            return;
199
        }
200
201
        $this->setAttr('class', implode(' ', $classArr));
202
    }
203
204
    /**
205
     * Get the value of a computed style property
206
     *
207
     * @param string $name
208
     *
209
     * @return string|null
210
     */
211
    public function getCss(string $name)
212
    {
213
        $style = (string) $this->getAttr('style');
214
        $css = Helper::splitCss($style);
215
        if (!$css) {
216
            return null;
217
        }
218
219
        if (array_key_exists($name, $css)) {
220
            return $css[$name];
221
        }
222
223
        $arr = array_change_key_case($css, CASE_LOWER);
224
        $key = strtolower($name);
225
        if (array_key_exists($key, $arr)) {
226
            return $arr[$key];
227
        }
228
229
        return null;
230
    }
231
232
    /**
233
     * Set or Remove one CSS property.
234
     *
235
     * @param string      $name
236
     * @param string|null $value
237
     */
238
    public function setCss(string $name, ?string $value)
239
    {
240
        if ((string) $value === '') {
241
            $this->removeCss($name);
242
            return;
243
        }
244
245
        $style = (string) $this->getAttr('style');
246
        if (!$style) {
247
            $this->setAttr('style', $name . ': ' . $value . ';');
248
            return;
249
        }
250
251
        $css = Helper::splitCss($style);
252
        if (!array_key_exists($name, $css)) {
253
            $allKeys = array_keys($css);
254
            $arr = array_combine(
255
                $allKeys,
256
                array_map('strtolower', $allKeys)
257
            ) ?: [];
258
259
            $keys = array_keys($arr, strtolower($name));
260
            foreach ($keys as $key) {
261
                unset($css[$key]);
262
            }
263
        }
264
265
        $css[$name] = $value;
266
        $style = Helper::implodeCss($css);
267
        $this->setAttr('style', $style);
268
    }
269
270
    /**
271
     * Remove one CSS property.
272
     *
273
     * @param string $name
274
     */
275
    public function removeCss(string $name)
276
    {
277
        $style = (string) $this->getAttr('style');
278
        if (!$style) {
279
            return;
280
        }
281
282
        $css = Helper::splitCss($style);
283
        $removed = false;
284
        if (array_key_exists($name, $css)) {
285
            unset($css[$name]);
286
            $removed = true;
287
        } else {
288
            $allKeys = array_keys($css);
289
            $arr = array_combine(
290
                $allKeys,
291
                array_map('strtolower', $allKeys)
292
            ) ?: [];
293
294
            $keys = array_keys($arr, strtolower($name));
295
            foreach ($keys as $key) {
296
                unset($css[$key]);
297
                $removed = true;
298
            }
299
        }
300
301
        if ($removed) {
302
            $style = Helper::implodeCss($css);
303
            $this->setAttr('style', $style);
304
        }
305
    }
306
307
    /**
308
     * Get the current value of the node.
309
     *
310
     * @return string|null
311
     */
312
    public function getVal()
313
    {
314
        switch ($this->node->tagName) {
315
            case 'input':
316
                return $this->node->getAttribute('value');
317
            case 'textarea':
318
                return $this->node->nodeValue;
319
            case 'select':
320
321
                $xpaths = [
322
                    Helper::toXpath('option:selected', 'child::'),
323
                    'child::option[1]'
324
                ];
325
                foreach ($xpaths as $xpath) {
326
                    $nodes = Helper::xpathQuery(
327
                        $xpath,
328
                        $this->getDoc(),
329
                        $this->node
330
                    );
331
332
                    if (count($nodes)) {
333
                        return $nodes[0]->getAttribute('value');
334
                    }
335
                }
336
337
                break;
338
        }
339
340
        return null;
341
    }
342
343
    /**
344
     * Set the value of the node.
345
     *
346
     * @param string $value
347
     */
348
    public function setVal(string $value)
349
    {
350
        switch ($this->node->tagName) {
351
            case 'input':
352
                $this->node->setAttribute('value', $value);
353
                break;
354
            case 'textarea':
355
                $this->node->nodeValue = $value;
356
                break;
357
            case 'select':
358
                $nodes = Helper::xpathQuery(
359
                    Helper::toXpath('option:selected', 'child::'),
360
                    $this->getDoc(),
361
                    $this->node
362
                );
363
364
                foreach ($nodes as $node) {
365
                    $node->removeAttribute('selected');
366
                }
367
368
                $nodes = Helper::xpathQuery(
369
                    Helper::toXpath("option[value='{$value}']", 'child::'),
370
                    $this->getDoc(),
371
                    $this->node
372
                );
373
374
                if (count($nodes)) {
375
                    $nodes[0]->setAttribute('selected', 'selected');
376
                }
377
378
                break;
379
        }
380
    }
381
}
382