Passed
Push — master ( 60319d...b48e8d )
by Daniele
08:21
created

FluidContext::resolveQuery()   F

Complexity

Conditions 14
Paths 576

Size

Total Lines 22
Code Lines 13

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 22
rs 3.5949
cc 14
eloc 13
nc 576
nop 1

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace FluidXml;
4
5
/**
6
 * @method array array()
7
 */
8
class FluidContext implements FluidInterface, \ArrayAccess, \Iterator
9
{
10
        use NewableTrait,
11
            FluidSaveTrait,
12
            ReservedCallTrait,          // For compatibility with PHP 5.6.
13
            ReservedCallStaticTrait;    // For compatibility with PHP 5.6.
14
15
        private $document;
16
        private $handler;
17
        private $nodes = [];
18
        private $seek = 0;
19
20
        public function __construct($document, $handler, $context)
21
        {
22
                $this->document = $document;
23
                $this->handler  = $handler;
24
25
                if (! \is_array($context) && ! $context instanceof \Traversable) {
26
                        // DOMDocument, DOMElement and DOMNode are not iterable.
27
                        // DOMNodeList and FluidContext are iterable.
28
                        $context = [ $context ];
29
                }
30
31
                foreach ($context as $n) {
32
                        if (! $n instanceof \DOMNode) {
33
                                throw new \Exception('Node type not recognized.');
34
                        }
35
36
                        $this->nodes[] = $n;
37
                }
38
        }
39
40
        // \ArrayAccess interface.
41
        public function offsetSet($offset, $value)
42
        {
43
                // if (\is_null($offset)) {
44
                //         $this->nodes[] = $value;
45
                // } else {
46
                //         $this->nodes[$offset] = $value;
47
                // }
48
                throw new \Exception('Setting a context element is not allowed.');
49
        }
50
51
        // \ArrayAccess interface.
52
        public function offsetExists($offset)
53
        {
54
                return isset($this->nodes[$offset]);
55
        }
56
57
        // \ArrayAccess interface.
58
        public function offsetUnset($offset)
59
        {
60
                // unset($this->nodes[$offset]);
61
                \array_splice($this->nodes, $offset, 1);
62
        }
63
64
        // \ArrayAccess interface.
65
        public function offsetGet($offset)
66
        {
67
                if (isset($this->nodes[$offset])) {
68
                        return $this->nodes[$offset];
69
                }
70
71
                return null;
72
        }
73
74
        // \Iterator interface.
75
        public function rewind()
76
        {
77
                $this->seek = 0;
78
        }
79
80
        // \Iterator interface.
81
        public function current()
82
        {
83
                return $this->nodes[$this->seek];
84
        }
85
86
        // \Iterator interface.
87
        public function key()
88
        {
89
                return $this->seek;
90
        }
91
92
        // \Iterator interface.
93
        public function next()
94
        {
95
                ++$this->seek;
96
        }
97
98
        // \Iterator interface.
99
        public function valid()
100
        {
101
                return isset($this->nodes[$this->seek]);
102
        }
103
104
        public function length()
105
        {
106
                return \count($this->nodes);
107
        }
108
109
        // Alias of ->length().
110
        public function size()
111
        {
112
                return $this->length();
113
        }
114
115
        public function query(...$query)
116
        {
117
                if (\is_array($query[0])) {
118
                        $query = $query[0];
119
                }
120
121
                $results = [];
122
123
                $xp = $this->document->xpath;
124
125
                foreach ($this->nodes as $n) {
126
                        foreach ($query as $q) {
127
                                $q = $this->resolveQuery($q);
128
129
                                // Returns a DOMNodeList.
130
                                $res = $xp->query($q, $n);
131
132
                                // Algorithm 1:
133
                                // $results = \array_merge($results, \iterator_to_array($res));
134
135
                                // Algorithm 2:
136
                                // It is faster than \iterator_to_array and a lot faster
137
                                // than \iterator_to_array + \array_merge.
138
                                foreach ($res as $r) {
139
                                        $results[] = $r;
140
                                }
141
142
                                // Algorithm 3:
143
                                // for ($i = 0, $l = $res->length; $i < $l; ++$i) {
144
                                //         $results[] = $res->item($i);
145
                                // }
146
                        }
147
                }
148
149
                // Performing over multiple sibling nodes a query that ascends
150
                // the xpath, relative (../..) or absolute (//), returns identical
151
                // matching results that must be collapsed in an unique result
152
                // otherwise a subsequent operation is performed multiple times.
153
                $results = $this->filterQueryResults($results);
154
155
                return $this->newContext($results);
156
        }
157
158
        public function __invoke(...$query)
159
        {
160
                return $this->query(...$query);
161
        }
162
163
        public function times($times, callable $fn = null)
164
        {
165
                if ($fn === null) {
166
                        return new FluidRepeater($this->document, $this->handler, $this, $times);
167
                }
168
169
                for ($i = 0; $i < $times; ++$i) {
170
                        $this->callfn($fn, [$this, $i]);
171
                }
172
173
                return $this;
174
        }
175
176
        public function each(callable $fn)
177
        {
178
                foreach ($this->nodes as $i => $n) {
179
                        $cx = $this->newContext($n);
180
181
                        $this->callfn($fn, [$cx, $i, $n]);
182
                }
183
184
                return $this;
185
        }
186
187
        public function filter(callable $fn)
188
        {
189
                $nodes = [];
190
191
                foreach ($this->nodes as $i => $n) {
192
                        $cx = $this->newContext($n);
193
194
                        $ret = $this->callfn($fn, [$cx, $i, $n]);
195
196
                        if ($ret !== false) {
197
                                $nodes[] = $n;
198
                        }
199
                }
200
201
                return $this->newContext($nodes);
202
        }
203
204
        // addChild($child, $value?, $attributes? = [], $switchContext? = false)
205
        public function addChild($child, ...$optionals)
206
        {
207
                return $this->handler->insertElement($this->nodes, $child, $optionals, function ($parent, $element) {
208
                        return $parent->appendChild($element);
209
                }, $this);
210
        }
211
212
        // Alias of ->addChild().
213
        public function add($child, ...$optionals)
214
        {
215
                return $this->addChild($child, ...$optionals);
216
        }
217
218
        public function prependSibling($sibling, ...$optionals)
219
        {
220
                return $this->handler->insertElement($this->nodes, $sibling, $optionals, function ($sibling, $element) {
221
                        return $sibling->parentNode->insertBefore($element, $sibling);
222
                }, $this);
223
        }
224
225
        // Alias of ->prependSibling().
226
        public function prepend($sibling, ...$optionals)
227
        {
228
                return $this->prependSibling($sibling, ...$optionals);
229
        }
230
231
        // Alias of ->prependSibling().
232
        public function insertSiblingBefore($sibling, ...$optionals)
233
        {
234
                return $this->prependSibling($sibling, ...$optionals);
235
        }
236
237
        public function appendSibling($sibling, ...$optionals)
238
        {
239
                return $this->handler->insertElement($this->nodes, $sibling, $optionals, function ($sibling, $element) {
240
                        // If ->nextSibling is null, $element is simply appended as last sibling.
241
                        return $sibling->parentNode->insertBefore($element, $sibling->nextSibling);
242
                }, $this);
243
        }
244
245
        // Alias of ->appendSibling().
246
        public function append($sibling, ...$optionals)
247
        {
248
                return $this->appendSibling($sibling, ...$optionals);
249
        }
250
251
        // Arguments can be in the form of:
252
        // setAttribute($name, $value)
253
        // setAttribute(['name' => 'value', ...])
254
        public function setAttribute(...$arguments)
255
        {
256
                // Default case is:
257
                // [ 'name' => 'value', ... ]
258
                $attrs = $arguments[0];
259
260
                // If the first argument is not an array,
261
                // the user has passed two arguments:
262
                // 1. is the attribute name
263
                // 2. is the attribute value
264
                if (! \is_array($arguments[0])) {
265
                        $val = isset($arguments[1]) ? $arguments[1]: '';
266
                        $attrs = [$arguments[0] => $val];
267
                }
268
269
                foreach ($this->nodes as $n) {
270
                        foreach ($attrs as $k => $v) {
271
                                if (\is_integer($k)) {
272
                                        $k = $v;
273
                                        $v = null;
274
                                }
275
276
                                // Algorithm 1:
277
                                $n->setAttribute($k, $v);
278
279
                                // Algorithm 2:
280
                                // $n->setAttributeNode(new \DOMAttr($k, $v));
281
282
                                // Algorithm 3:
283
                                // $n->appendChild(new \DOMAttr($k, $v));
284
285
                                // Algorithm 2 and 3 have a different behaviour
286
                                // from Algorithm 1.
287
                                // The attribute is still created or setted, but
288
                                // changing the value of an existing attribute
289
                                // changes even the order of that attribute
290
                                // in the attribute list.
291
                        }
292
                }
293
294
                return $this;
295
        }
296
297
        // Alias of ->setAttribute().
298
        public function attr(...$arguments)
299
        {
300
                return $this->setAttribute(...$arguments);
301
        }
302
303
        public function setText($text)
304
        {
305
                foreach ($this->nodes as $n) {
306
                        // Algorithm 1:
307
                        $n->nodeValue = $text;
308
309
                        // Algorithm 2:
310
                        // foreach ($n->childNodes as $c) {
311
                        //         $n->removeChild($c);
312
                        // }
313
                        // $n->appendChild(new \DOMText($text));
314
315
                        // Algorithm 3:
316
                        // foreach ($n->childNodes as $c) {
317
                        //         $n->replaceChild(new \DOMText($text), $c);
318
                        // }
319
                }
320
321
                return $this;
322
        }
323
324
        // Alias of ->setText().
325
        public function text($text)
326
        {
327
                return $this->setText($text);
328
        }
329
330
        public function addText($text)
331
        {
332
                foreach ($this->nodes as $n) {
333
                        $n->appendChild(new \DOMText($text));
334
                }
335
336
                return $this;
337
        }
338
339
        public function setCdata($text)
340
        {
341
                foreach ($this->nodes as $n) {
342
                        $n->nodeValue = '';
343
                        $n->appendChild(new \DOMCDATASection($text));
344
                }
345
346
                return $this;
347
        }
348
349
        // Alias of ->setCdata().
350
        public function cdata($text)
351
        {
352
                return $this->setCdata($text);
353
        }
354
355
        public function addCdata($text)
356
        {
357
                foreach ($this->nodes as $n) {
358
                        $n->appendChild(new \DOMCDATASection($text));
359
                }
360
361
                return $this;
362
        }
363
364
        public function setComment($text)
365
        {
366
                foreach ($this->nodes as $n) {
367
                        $n->nodeValue = '';
368
                        $n->appendChild(new \DOMComment($text));
369
                }
370
371
                return $this;
372
        }
373
374
        // Alias of ->setComment().
375
        public function comment($text)
376
        {
377
                return $this->setComment($text);
378
        }
379
380
        public function addComment($text)
381
        {
382
                foreach ($this->nodes as $n) {
383
                        $n->appendChild(new \DOMComment($text));
384
                }
385
386
                return $this;
387
        }
388
389
        public function remove(...$query)
390
        {
391
                // Arguments can be empty, a string or an array of strings.
392
393
                if (empty($query)) {
394
                        // The user has requested to remove the nodes of this context.
395
                        $targets = $this->nodes;
396
                } else {
397
                        $targets = $this->query(...$query);
398
                }
399
400
                foreach ($targets as $t) {
401
                        $t->parentNode->removeChild($t);
402
                }
403
404
                return $this;
405
        }
406
407
        public function dom()
408
        {
409
                return $this->document->dom;
410
        }
411
412
        // This method should be called 'array',
413
        // but for compatibility with PHP 5.6
414
        // it is shadowed by the __call() method.
415
        public function array_()
416
        {
417
                return $this->nodes;
418
        }
419
420
        public function __toString()
421
        {
422
                return $this->xml();
423
        }
424
425
        public function xml($strip = false)
426
        {
427
                return FluidHelper::domnodesToString($this->nodes);
428
        }
429
430
        public function html($strip = false)
431
        {
432
                return FluidHelper::domnodesToString($this->nodes, true);
433
        }
434
435
        protected function newContext(&$context)
436
        {
437
                return new FluidContext($this->document, $this->handler, $context);
438
        }
439
440
        protected function resolveQuery($query)
441
        {
442
                $matched = false;
443
444
                $is_dot   = $query === '.';
445
                $matched  = $matched || $is_dot;
446
447
                $slash    = ! $matched && $query[0] === '/';
448
                $matched  = $matched || $slash;
449
450
                $dotslash = ! $matched && $query[0] === '.' && $query[1] === '/';
451
                $matched  = $matched || $dotslash;
452
453
                $dotdot   = ! $matched && $query[0] === '.' && $query[1] === '.';
454
                $matched  = $matched || $dotdot;
0 ignored issues
show
Unused Code introduced by
$matched is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
455
456
                if ($is_dot || $slash || $dotslash || $dotdot) {
457
                        return $query;
458
                }
459
460
                return CssTranslator::xpath($query);
461
        }
462
463
        protected function filterQueryResults(&$results)
464
        {
465
                $set = [];
466
467
                foreach ($results as $r) {
468
                        $found = false;
469
470
                        foreach ($set as $u) {
471
                                $found = ($r === $u) || $found;
472
                        }
473
474
                        if (! $found) {
475
                                $set[] = $r;
476
                        }
477
                }
478
479
                return $set;
480
        }
481
482
        protected function callfn($fn, $args)
483
        {
484
                if ($fn instanceof \Closure) {
485
                        $bind = \array_shift($args);
486
487
                        $fn = $fn->bindTo($bind);
488
489
                        // It is faster than \call_user_func.
490
                        return $fn(...$args);
491
                }
492
493
                return \call_user_func($fn, ...$args);
494
        }
495
}
496