Selector::match()   C
last analyzed

Complexity

Conditions 16
Paths 14

Size

Total Lines 50
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 16.5494

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 16
eloc 32
c 1
b 0
f 0
nc 14
nop 1
dl 0
loc 50
ccs 27
cts 31
cp 0.871
crap 16.5494
rs 5.5666

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
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