SliceElementMatcher::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

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