Completed
Push — master ( 6e010a...c5b995 )
by Thomas
04:09
created

Selection::count()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
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;
19
20
    /**
21
     * Return DOMDocument
22
     *
23
     * @return DOMDocument
24
     */
25 1
    public function getDoc(): DOMDocument
26
    {
27 1
        return $this->doc;
28
    }
29
30
    /**
31
     * Return DOMNodes
32
     *
33
     * @return DOMNode[]
34
     */
35 74
    public function getNodes(): array
36
    {
37 74
        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 44
    public function each(Closure $function, bool $reverse = false)
49
    {
50 44
        $class = $this->getClosureClass($function, 0);
51
52 44
        $nodes = $reverse ? array_reverse($this->nodes, true) : $this->nodes;
53 44
        foreach ($nodes as $index => $node) {
54 44
            $node = $this->closureResolve($class, $node);
55
56 44
            if (!empty($node)) {
57 44
                $function($node, $index);
58
            }
59
        }
60
61 44
        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 3
    public function map(Closure $function)
73
    {
74 3
        $class = $this->getClosureClass($function, 0);
75
76 3
        $data = [];
77 3
        foreach ($this->nodes as $index => $node) {
78 3
            $node = $this->closureResolve($class, $node);
79 3
            $data[] = !empty($node) ? $function($node, $index) : null;
80
        }
81
82 3
        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 5
    public function mapAnyTrue(Closure $function)
94
    {
95 5
        $class = $this->getClosureClass($function, 0);
96
97 5
        foreach ($this->nodes as $index => $node) {
98 5
            $node = $this->closureResolve($class, $node);
99 5
            if (!empty($node) && $function($node, $index)) {
100 5
                return true;
101
            }
102
        }
103
104 5
        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 61
    public function mapFirst(Closure $function)
116
    {
117 61
        if (!$this->count()) {
118 4
            return null;
119
        }
120
121 61
        $class = $this->getClosureClass($function, 0);
122 61
        $node = $this->closureResolve($class, $this->nodes[0]);
123
124 61
        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 25
    public function eq(int $index)
135
    {
136 25
        $node = array_key_exists($index, $this->nodes)
137 25
            ? $this->nodes[$index]
138 25
            : [];
139
140 25
        return $this->resolve($node);
141
    }
142
143
    /**
144
     * Reduce the current nodes to the first one.
145
     *
146
     * @return static
147
     */
148 2
    public function first()
149
    {
150 2
        return $this->eq(0);
151
    }
152
153
    /**
154
     * Reduce the current nodes to the final one.
155
     *
156
     * @return static
157
     */
158 2
    public function last()
159
    {
160 2
        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 2
    public function slice(int $offset, ?int $length = null)
173
    {
174 2
        return $this->resolve(array_slice($this->nodes, $offset, $length));
175
    }
176
177
    /**
178
     * Return DOMNodes
179
     *
180
     * @return DOMNode[]
181
     */
182 73
    public function toArray(): array
183
    {
184 73
        return $this->getNodes();
185
    }
186
187 1
    public function unset(int $offset)
188
    {
189 1
        unset($this->nodes[$offset]);
190 1
        $this->nodes = array_values($this->nodes);
191 1
    }
192
193 70
    public function count(): int
194
    {
195 70
        return count($this->toArray());
196
    }
197
198 2
    public function getIterator()
199
    {
200 2
        return new ArrayIterator($this->toArray());
201
    }
202
203 1
    public function offsetSet($offset, $value)
204
    {
205 1
        if (!($value instanceof DOMNode)) {
206 1
            throw new Exception(
207 1
                'Expect an instance of DOMNode, ' . gettype($value) . ' given.'
208
            );
209
        }
210
211 1
        if (!in_array($value, $this->nodes, true)) {
212 1
            $this->nodes[] = $value;
213
        }
214 1
    }
215
216 1
    public function offsetExists($offset)
217
    {
218 1
        return isset($this->nodes[$offset]);
219
    }
220
221 1
    public function offsetUnset($offset)
222
    {
223 1
        $this->unset($offset);
224 1
    }
225
226 14
    public function offsetGet($offset)
227
    {
228 14
        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 70
    protected function getClosureClass(Closure $function, int $index)
242
    {
243 70
        $reflection = new ReflectionFunction($function);
244 70
        $parameters = $reflection->getParameters();
245
246 70
        if (!empty($parameters) && array_key_exists($index, $parameters)) {
247 70
            $class = $parameters[$index]->getClass();
248 70
            if (!empty($class)) {
249 68
                return $class->getName();
250
            }
251
        }
252
253 4
        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 70
    protected function closureResolve(string $class, DOMNode $node)
266
    {
267 70
        if ($class === static::class) {
268 6
            return $this->resolve($node);
269 70
        } elseif ($class === HtmlElement::class) {
270 29
            if (!($node instanceof DOMElement)) {
271 1
                return null;
272
            }
273 28
            return new HtmlElement($node);
274 59
        } elseif ($class === HtmlNode::class) {
275 54
            return new HtmlNode($node);
276
        }
277
278 24
        return $node;
279
    }
280
}
281