Passed
Push — master ( fc0629...9e5c60 )
by Edward
05:11
created

SliceElementMatcher   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 101
Duplicated Lines 0 %

Test Coverage

Coverage 90.2%

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 45
dl 0
loc 101
ccs 46
cts 51
cp 0.902
rs 10
c 1
b 0
f 1
wmc 29

9 Methods

Rating   Name   Duplication   Size   Complexity  
A match() 0 15 5
A __construct() 0 7 1
A findArrayLength() 0 9 4
A detectStart() 0 11 4
A isInRange() 0 5 4
A detectEnd() 0 13 6
A isOnStep() 0 3 1
A getIndex() 0 3 2
A getSortIndex() 0 8 2
1
<?php
2
declare(strict_types=1);
3
4
namespace Remorhaz\JSON\Path\Runtime\Matcher;
5
6
use Remorhaz\JSON\Data\Value\ArrayValueInterface;
7
use function is_int;
8
use function iterator_count;
9
use function max;
10
use Remorhaz\JSON\Data\Value\NodeValueInterface;
11
12
final class SliceElementMatcher implements SortedChildMatcherInterface
13
{
14
15
    private $start;
16
17
    private $end;
18
19
    private $step;
20
21
    private $isReverse;
22
23 28
    public function __construct(?int $start, ?int $end, ?int $step)
24
    {
25 28
        $this->step = $step ?? 1;
26 28
        $this->isReverse = $this->step < 0;
27
28 28
        $this->start = $start;
29 28
        $this->end = $end;
30 28
    }
31
32 28
    public function match($address, NodeValueInterface $value, NodeValueInterface $container): bool
33
    {
34 28
        if (0 == $this->step || !is_int($address)) {
35 3
            return false;
36
        }
37
38 25
        $count = $this->findArrayLength($container);
39 25
        if (!isset($count)) {
40 2
            return false;
41
        }
42
43 23
        $start = $this->detectStart($count);
44 23
        $end = $this->detectEnd($count);
45
46 23
        return $this->isInRange($address, $start, $end) && $this->isOnStep($address, $start);
47
    }
48
49 25
    private function findArrayLength(NodeValueInterface $value): ?int
50
    {
51 25
        $count = $value instanceof ArrayValueInterface
52 24
            ? iterator_count($value->createChildIterator())
53 25
            : null;
54
55 25
        return isset($count) && $count > 0
56 23
            ? $count
57 25
            : null;
58
    }
59
60 23
    private function detectStart(int $count): int
61
    {
62 23
        $start = $this->start;
63 23
        if (!isset($start)) {
64 14
            $start = $this->isReverse ? -1 : 0;
65
        }
66 23
        if ($start < 0) {
67 7
            $start = max($start + $count, 0);
68
        }
69
70 23
        return $start;
71
    }
72
73 23
    private function detectEnd(int $count): int
74
    {
75 23
        $end = $this->end;
76 23
        if (!isset($end)) {
77 15
            $end = $this->isReverse ? -$count - 1 : $count;
78
        }
79 23
        if ($end > $count) {
80 1
            return $count;
81
        }
82
83 22
        return $end < 0
84 7
            ? max($end + $count, $this->isReverse ? -1 : 0)
85 22
            : $end;
86
    }
87
88 23
    private function isInRange(int $address, int $start, int $end): bool
89
    {
90 23
        return $this->isReverse
91 10
            ? $address <= $start && $address > $end
92 23
            : $address >= $start && $address < $end;
93
    }
94
95 11
    private function isOnStep(int $address, int $start): bool
96
    {
97 11
        return 0 == $this->getIndex($address, $start) % $this->step;
98
    }
99
100 11
    private function getIndex(int $address, int $start): int
101
    {
102 11
        return $this->isReverse ? $start - $address : $address - $start;
103
    }
104
105
    public function getSortIndex($address, NodeValueInterface $value, NodeValueInterface $container): int
106
    {
107
        $count = $this->findArrayLength($container);
108
        if (!isset($count)) {
109
            throw new Exception\AddressNotSortableException($address);
110
        }
111
112
        return $this->getIndex($address, $this->detectStart($count));
113
    }
114
}
115