Passed
Push — master ( 48311f...4a44e3 )
by Chris
02:30
created

Pointer::decodePath()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4.0218

Importance

Changes 0
Metric Value
dl 0
loc 17
ccs 8
cts 9
cp 0.8889
rs 9.2
c 0
b 0
f 0
cc 4
eloc 8
nc 4
nop 1
crap 4.0218
1
<?php declare(strict_types=1);
2
3
namespace DaveRandom\Jom;
4
5
use DaveRandom\Jom\Exceptions\InvalidPointerException;
6
7
final class Pointer
8
{
9
    private $path = [];
10
    private $relativeLevels = null;
11
    private $keyLookup = false;
12
13
    private $string;
14
15
    /**
16
     * @throws InvalidPointerException
17
     */
18 35
    private static function decodePath(string $path): array
19
    {
20 35
        if ($path === '') {
21 15
            return [];
22
        }
23
24 20
        if ($path[0] !== '/') {
25
            throw new InvalidPointerException('JSON pointer path must be the empty string or begin with /');
26
        }
27
28 20
        $result = [];
29
30 20
        foreach (\explode('/', \substr($path, 1)) as $component) {
31 20
            $result[] = \str_replace(['~1', '~0'], ['/', '~'], $component);
32
        }
33
34 20
        return $result;
35
    }
36
37 22
    private static function encodePath(array $path): string
38
    {
39 22
        $result = '';
40
41 22
        foreach ($path as $component) {
42 14
            $result .= '/' . \str_replace(['~', '/'], ['~0', '~1'], $component);
43
        }
44
45 22
        return $result;
46
    }
47
48 49
    private static function splitRelativePointerComponents(string $pointer): array
49
    {
50 49
        return \preg_match('/^(0|[1-9][0-9]*)($|[^0-9].*)/i', $pointer, $match)
51 42
            ? [$match[2], (int)$match[1]]
52 49
            : [$pointer, null];
53
    }
54
55
    /**
56
     * @throws InvalidPointerException
57
     */
58 11
    private static function validateRelativePointerParams(?int $relativeLevels, ?bool $isKeyLookup): void
59
    {
60 11
        if ($relativeLevels < 0) {
61
            throw new InvalidPointerException('Relative levels must be positive');
62
        }
63
64 11
        if ($isKeyLookup && $relativeLevels === null) {
65
            throw new InvalidPointerException('Key lookups are only valid for relative pointers');
66
        }
67
    }
68
69
    private function __construct() { }
70
71
    /**
72
     * @throws InvalidPointerException
73
     */
74 49
    public static function createFromString(string $pointer): self
75
    {
76 49
        $result = new self();
77
78 49
        [$path, $result->relativeLevels] = self::splitRelativePointerComponents($pointer);
79
80 49
        $result->keyLookup = $result->relativeLevels !== null && $path === '#';
81
82 49
        if (!$result->keyLookup) {
83 35
            $result->path = self::decodePath($path);
84
        }
85
86 49
        return $result;
87
    }
88
89
    /**
90
     * @param string[] $path
91
     * @throws InvalidPointerException
92
     */
93 11
    public static function createFromParameters(array $path, ?int $relativeLevels = null, ?bool $isKeyLookup = false): self
94
    {
95 11
        self::validateRelativePointerParams($relativeLevels, $isKeyLookup);
96
97 11
        $result = new self();
98
99 11
        $result->relativeLevels = $relativeLevels;
100 11
        $result->keyLookup = $isKeyLookup ?? false;
101
102 11
        foreach ($path as $component) {
103 7
            $result->path[] = (string)$component;
104
        }
105
106 11
        return $result;
107
    }
108
109 37
    public function getPath(): array
110
    {
111 37
        return $this->path;
112
    }
113
114 42
    public function getRelativeLevels(): ?int
115
    {
116 42
        return $this->relativeLevels;
117
    }
118
119 49
    public function isRelative(): bool
120
    {
121 49
        return $this->relativeLevels !== null;
122
    }
123
124 30
    public function isKeyLookup(): bool
125
    {
126 30
        return $this->keyLookup;
127
    }
128
129 26
    public function __toString(): string
130
    {
131 26
        if (isset($this->string)) {
132
            return $this->string;
133
        }
134
135 26
        $this->string = '';
136
137 26
        if ($this->relativeLevels !== null) {
138 19
            $this->string .= $this->relativeLevels;
139
        }
140
141 26
        $this->string .= $this->keyLookup
142 4
            ? '#'
143 22
            : self::encodePath($this->path);
144
145 26
        return $this->string;
146
    }
147
}
148