Completed
Branch scrutinizer (607e7a)
by Thomas
02:04
created

Selection   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 263
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 64
c 1
b 0
f 0
dl 0
loc 263
rs 9.1199
wmc 41

20 Methods

Rating   Name   Duplication   Size   Complexity  
A getNodes() 0 3 1
A getDoc() 0 3 1
A offsetGet() 0 3 2
A offsetUnset() 0 3 1
A last() 0 3 1
A mapFirst() 0 10 3
A eq() 0 7 2
A each() 0 14 4
A offsetExists() 0 3 1
A getClosureClass() 0 13 4
A toArray() 0 3 1
A getIterator() 0 3 1
A map() 0 11 3
A slice() 0 3 1
A unset() 0 4 1
A offsetSet() 0 10 3
A count() 0 3 1
A mapAnyTrue() 0 12 4
A closureResolve() 0 14 5
A first() 0 3 1

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
namespace Sulao\HtmlQuery;
4
5
use ArrayAccess, ArrayIterator;
6
use Closure, Countable;
7
use DOMDocument, DOMNode, DOMElement;
8
use IteratorAggregate;
9
use ReflectionFunction;
10
11
/**
12
 * Class Selection
13
 *
14
 * @package Sulao\HtmlQuery
15
 */
16
abstract class Selection implements Countable, IteratorAggregate, ArrayAccess
17
{
18
    use Selector;
0 ignored issues
show
Bug introduced by
The trait Sulao\HtmlQuery\Selector requires the property $nodeType which is not provided by Sulao\HtmlQuery\Selection.
Loading history...
19
20
    /**
21
     * Return DOMDocument
22
     *
23
     * @return DOMDocument
24
     */
25
    public function getDoc(): DOMDocument
26
    {
27
        return $this->doc;
28
    }
29
30
    /**
31
     * Return DOMNodes
32
     *
33
     * @return DOMNode[]
34
     */
35
    public function getNodes(): array
36
    {
37
        return $this->nodes;
38
    }
39
40
    /**
41
     * Iterate over the matched nodes, executing the function for each node.
42
     *
43
     * @param Closure $function function(DOMNode|HtmlQuery $node, $index)
44
     * @param bool    $reverse  Iterate over the nodes reversely
45
     *
46
     * @return static
47
     */
48
    public function each(Closure $function, bool $reverse = false)
49
    {
50
        $class = $this->getClosureClass($function, 0);
51
52
        $nodes = $reverse ? array_reverse($this->nodes, true) : $this->nodes;
53
        foreach ($nodes as $index => $node) {
54
            $node = $this->closureResolve($class, $node);
55
56
            if (!empty($node)) {
57
                $function($node, $index);
58
            }
59
        }
60
61
        return $this;
62
    }
63
64
    /**
65
     * Pass each matched node through a function,
66
     * producing an array containing the return values.
67
     *
68
     * @param Closure $function function($index, DOMNode|HtmlQuery $node)
69
     *
70
     * @return array
71
     */
72
    public function map(Closure $function)
73
    {
74
        $class = $this->getClosureClass($function, 0);
75
76
        $data = [];
77
        foreach ($this->nodes as $index => $node) {
78
            $node = $this->closureResolve($class, $node);
79
            $data[] = !empty($node) ? $function($node, $index) : null;
80
        }
81
82
        return $data;
83
    }
84
85
    /**
86
     * Pass each matched node through a function,
87
     * Break and return true when the function with the first node return true.
88
     *
89
     * @param Closure $function function($index, DOMNode|HtmlQuery $node)
90
     *
91
     * @return bool
92
     */
93
    public function mapAnyTrue(Closure $function)
94
    {
95
        $class = $this->getClosureClass($function, 0);
96
97
        foreach ($this->nodes as $index => $node) {
98
            $node = $this->closureResolve($class, $node);
99
            if (!empty($node) && $function($node, $index)) {
100
                return true;
101
            }
102
        }
103
104
        return false;
105
    }
106
107
    /**
108
     * Pass the first matched node through a function,
109
     * and return the return value of the function.
110
     *
111
     * @param Closure $function function(DOMNode|HtmlQuery $node)
112
     *
113
     * @return mixed|null
114
     */
115
    public function mapFirst(Closure $function)
116
    {
117
        if (!$this->count()) {
118
            return null;
119
        }
120
121
        $class = $this->getClosureClass($function, 0);
122
        $node = $this->closureResolve($class, $this->nodes[0]);
123
124
        return !empty($node) ? $function($node) : null;
125
    }
126
127
    /**
128
     * Reduce the current nodes to the one at the specified index.
129
     *
130
     * @param int $index
131
     *
132
     * @return static
133
     */
134
    public function eq(int $index)
135
    {
136
        $node = array_key_exists($index, $this->nodes)
137
            ? $this->nodes[$index]
138
            : [];
139
140
        return $this->resolve($node);
141
    }
142
143
    /**
144
     * Reduce the current nodes to the first one.
145
     *
146
     * @return static
147
     */
148
    public function first()
149
    {
150
        return $this->eq(0);
151
    }
152
153
    /**
154
     * Reduce the current nodes to the final one.
155
     *
156
     * @return static
157
     */
158
    public function last()
159
    {
160
        return $this->eq(count($this->nodes) - 1);
161
    }
162
163
164
    /**
165
     * Reduce the matched nodes to a subset specified by a range of indices.
166
     *
167
     * @param int      $offset
168
     * @param int|null $length
169
     *
170
     * @return static
171
     */
172
    public function slice(int $offset, ?int $length = null)
173
    {
174
        return $this->resolve(array_slice($this->nodes, $offset, $length));
175
    }
176
177
    /**
178
     * Return DOMNodes
179
     *
180
     * @return DOMNode[]
181
     */
182
    public function toArray(): array
183
    {
184
        return $this->getNodes();
185
    }
186
187
    public function unset(int $offset)
188
    {
189
        unset($this->nodes[$offset]);
190
        $this->nodes = array_values($this->nodes);
191
    }
192
193
    public function count(): int
194
    {
195
        return count($this->toArray());
196
    }
197
198
    public function getIterator()
199
    {
200
        return new ArrayIterator($this->toArray());
201
    }
202
203
    public function offsetSet($offset, $value)
204
    {
205
        if (!($value instanceof DOMNode)) {
206
            throw new Exception(
207
                'Expect an instance of DOMNode, ' . gettype($value) . ' given.'
208
            );
209
        }
210
211
        if (!in_array($value, $this->nodes, true)) {
212
            $this->nodes[] = $value;
213
        }
214
    }
215
216
    public function offsetExists($offset)
217
    {
218
        return isset($this->nodes[$offset]);
219
    }
220
221
    public function offsetUnset($offset)
222
    {
223
        $this->unset($offset);
224
    }
225
226
    public function offsetGet($offset)
227
    {
228
        return isset($this->nodes[$offset]) ? $this->nodes[$offset] : null;
229
    }
230
231
232
233
    /**
234
     * Get the class of the specified parameter of the closure.
235
     *
236
     * @param Closure $function
237
     * @param int     $index    Which parameter of the closure, starts with 0
238
     *
239
     * @return string
240
     */
241
    protected function getClosureClass(Closure $function, int $index)
242
    {
243
        $reflection = new ReflectionFunction($function);
244
        $parameters = $reflection->getParameters();
245
246
        if (!empty($parameters) && array_key_exists($index, $parameters)) {
247
            $class = $parameters[$index]->getClass();
248
            if ($class) {
0 ignored issues
show
introduced by
$class is of type ReflectionClass, thus it always evaluated to true.
Loading history...
249
                return $class->getName();
250
            }
251
        }
252
253
        return '';
254
    }
255
256
    /**
257
     * Resolve the node to static or HtmlElement instance or leaving it as DOMNode,
258
     * Then pass it to closure
259
     *
260
     * @param string  $class
261
     * @param DOMNode $node
262
     *
263
     * @return DOMNode|HtmlElement|HtmlNode|static|null
264
     */
265
    protected function closureResolve(string $class, DOMNode $node)
266
    {
267
        if ($class === static::class) {
268
            return $this->resolve($node);
269
        } elseif ($class === HtmlElement::class) {
270
            if (!($node instanceof DOMElement)) {
271
                return null;
272
            }
273
            return new HtmlElement($node);
274
        } elseif ($class === HtmlNode::class) {
275
            return new HtmlNode($node);
276
        }
277
278
        return $node;
279
    }
280
}
281