Selection   A
last analyzed

Complexity

Total Complexity 41

Size/Duplication

Total Lines 263
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

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

20 Methods

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

253
                return $class->/** @scrutinizer ignore-call */ getName();
Loading history...
254
            }
255
        }
256
257
        return '';
258
    }
259
260
    /**
261
     * Resolve the node to static or HtmlElement instance or leaving it as DOMNode,
262
     * Then pass it to closure
263
     *
264
     * @param string  $class
265 70
     * @param DOMNode $node
266
     *
267 70
     * @return DOMNode|HtmlElement|HtmlNode|static|null
268 6
     */
269 70
    protected function closureResolve(string $class, DOMNode $node)
270 29
    {
271 1
        if ($class === static::class) {
272
            return $this->resolve($node);
273 28
        } elseif ($class === HtmlElement::class) {
274 59
            if (!($node instanceof DOMElement)) {
275 54
                return null;
276
            }
277
            return new HtmlElement($node);
278 24
        } elseif ($class === HtmlNode::class) {
279
            return new HtmlNode($node);
280
        }
281
282
        return $node;
283
    }
284
}
285