Issues (9)

src/Selection.php (1 issue)

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

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