Selector   A
last analyzed

Complexity

Total Complexity 35

Size/Duplication

Total Lines 142
Duplicated Lines 0 %

Test Coverage

Coverage 95.06%

Importance

Changes 2
Bugs 1 Features 0
Metric Value
eloc 82
c 2
b 1
f 0
dl 0
loc 142
ccs 77
cts 81
cp 0.9506
rs 9.6
wmc 35

5 Methods

Rating   Name   Duplication   Size   Complexity  
A getSelector() 0 3 1
A __construct() 0 4 1
A getSelectorStack() 0 3 1
C create() 0 67 16
C match() 0 50 16
1
<?php
2
declare(strict_types=1);
3
4
5
namespace JsonDecodeStream\Internal;
6
7
use JsonDecodeStream\Exception\SelectorException;
8
use UnexpectedValueException;
9
10
class Selector
11
{
12
    /** @var static[] */
13
    protected static $cache = [];
14
    protected $selector;
15
    protected $selectorStack;
16
17 27
    protected function __construct(string $selector, array $selectorStack)
18
    {
19 27
        $this->selector = $selector;
20 27
        $this->selectorStack = $selectorStack;
21 27
    }
22
23 1
    public function getSelector(): string
24
    {
25 1
        return $this->selector;
26
    }
27
28 37
    public function getSelectorStack(): array
29
    {
30 37
        return $this->selectorStack;
31
    }
32
33 46
    public static function create(string $selector)
34
    {
35 46
        if (empty($selector)) {
36 1
            throw new SelectorException('selector should not be empty');
37
        }
38
39 45
        if (isset(static::$cache[$selector])) {
40 22
            return static::$cache[$selector];
41
        }
42
43 34
        $selectorStack = [];
44
45 34
        $offset = 0;
46 34
        while ($offset < strlen($selector)) {
47 34
            $part = substr($selector, $offset);
48 34
            $matches = null;
49 34
            if (preg_match('/^\[]/', $part, $matches)) {
50
                // `[]`
51 5
                array_push(
52 5
                    $selectorStack,
53 5
                    [ 'type' => 'any' ]
54
                );
55 33
            } elseif ($offset == 0 && preg_match('/^([a-z_\$][a-z0-9_\$]*)/i', $part, $matches)) {
56
                // `foo` but only as a beginning of selector
57 18
                array_push(
58 18
                    $selectorStack,
59 18
                    [ 'type' => 'key', 'key' => $matches[1] ]
60
                );
61 27
            } elseif (preg_match('/^\.([a-z_\$][a-z0-9_\$]*)/i', $part, $matches)) {
62
                // `.foo`
63 11
                array_push(
64 11
                    $selectorStack,
65 11
                    [ 'type' => 'key', 'key' => $matches[1] ]
66
                );
67 20
            } elseif (preg_match('/^\[(0|[1-9][0-9]*)]/', $part, $matches)) {
68
                // `[1]`
69 2
                array_push(
70 2
                    $selectorStack,
71 2
                    [ 'type' => 'index', 'index' => (int)$matches[1] ]
72
                );
73 18
            } elseif (preg_match('/^\[(0|[1-9][0-9]*)?:(0|[1-9][0-9]*)?]/', $part, $matches)) {
74
                // `[1:9]` or `[1:]` or `[:9]`
75 8
                $firstIndex = strlen($matches[1] ?? '') ? (int)$matches[1] : null;
76 8
                $lastIndex = strlen($matches[2] ?? '') ? (int)$matches[2] : null;
77 8
                if ($firstIndex === null && $lastIndex === null) {
78 1
                    throw new SelectorException('Wrong array range selector: "[:]"');
79
                }
80 7
                array_push(
81 7
                    $selectorStack,
82 7
                    [ 'type' => 'range', 'start' => $firstIndex, 'end' => $lastIndex ]
83
                );
84 10
            } elseif (preg_match('/^\["((?:[^"\\\\]+|\\\\.)*)"]/', $part, $matches)) {
85
                // `["foo"]` and also `["The \"Foo\" key"]`
86 4
                array_push(
87 4
                    $selectorStack,
88 4
                    [ 'type' => 'key', 'key' => $matches[1] ]
89
                );
90
            }
91
92 33
            if ($matches) {
93 29
                $offset += strlen($matches[0]);
94
            } else {
95 6
                throw new SelectorException('Wrong selector at `' . $part . '`');
96
            }
97
        }
98
99 27
        return static::$cache[$selector] = new static($selector, $selectorStack);
100
    }
101
102 21
    public function match(Stack $stack)
103
    {
104 21
        $stackFrames = $stack->frames();
105 21
        foreach ($this->getSelectorStack() as $i => $selStackFrame) {
106 21
            $stackFrame = $stackFrames[$i] ?? null;
107
108 21
            if ($stackFrame == null) {
109 16
                return false;
110
            }
111
112 21
            switch ($selStackFrame['type']) {
113 21
                case 'any':
114 8
                    break;
115
116 21
                case 'key':
117 21
                    if (!$stackFrame->isObject()) {
118
                        return false;
119
                    }
120 21
                    if ($stackFrame->getLastKey() !== $selStackFrame['key']) {
121 21
                        return false;
122
                    }
123 21
                    break;
124
125 3
                case 'index':
126 1
                    if (!$stackFrame->isArray()) {
127
                        return false;
128
                    }
129 1
                    if ($stackFrame->getLastKey() !== $selStackFrame['index']) {
130 1
                        return false;
131
                    }
132 1
                    break;
133
134 2
                case 'range':
135 2
                    if (!$stackFrame->isArray()) {
136
                        return false;
137
                    }
138 2
                    if ($selStackFrame['start'] !== null && $stackFrame->getLastKey() < $selStackFrame['start']) {
139 2
                        return false;
140
                    }
141 2
                    if ($selStackFrame['end'] !== null && $stackFrame->getLastKey() > $selStackFrame['end']) {
142 1
                        return false;
143
                    }
144 2
                    break;
145
146
                default:
147
                    throw new UnexpectedValueException($selStackFrame['type']);
148
            }
149
        }
150
151 21
        return true;
152
    }
153
}
154