Issues (16)

src/Json/JsonPointer.php (1 issue)

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the OpenapiBundle package.
7
 *
8
 * (c) Niels Nijens <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Nijens\OpenapiBundle\Json;
15
16
use Nijens\OpenapiBundle\Json\Exception\InvalidJsonPointerException;
17
use stdClass;
18
19
/**
20
 * Query a {@see stdClass} using JSON Pointer.
21
 *
22
 * @author Niels Nijens <[email protected]>
23
 */
24
class JsonPointer implements JsonPointerInterface
25
{
26
    /**
27
     * @var array
28
     */
29
    private const ESCAPE_CHARACTERS = [
30
        '~' => '~0',
31
        '/' => '~1',
32
    ];
33
34
    /**
35
     * @var stdClass
36
     */
37
    private $json;
38
39
    /**
40
     * Constructs a new {@see JsonPointer} instance.
41
     */
42
    public function __construct(?stdClass $json = null)
43
    {
44
        $this->json = $json;
45
    }
46
47
    /**
48
     * {@inheritdoc}
49
     */
50
    public function withJson(stdClass $json): JsonPointerInterface
51
    {
52
        return new self($json);
53
    }
54
55
    /**
56
     * {@inheritdoc}
57
     */
58
    public function has(string $pointer): bool
59
    {
60
        try {
61
            $this->traverseJson($pointer);
62
63
            return true;
64
        } catch (InvalidJsonPointerException $exception) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
65
        }
66
67
        return false;
68
    }
69
70
    /**
71
     * {@inheritdoc}
72
     */
73
    public function get(string $pointer)
74
    {
75
        return $this->traverseJson($pointer);
76
    }
77
78
    /**
79
     * {@inheritdoc}
80
     */
81
    public function &getByReference(string $pointer)
82
    {
83
        return $this->traverseJson($pointer);
84
    }
85
86
    /**
87
     * {@inheritdoc}
88
     */
89
    public function escape(string $value): string
90
    {
91
        return str_replace(
92
            array_keys(self::ESCAPE_CHARACTERS),
93
            array_values(self::ESCAPE_CHARACTERS),
94
            $value
95
        );
96
    }
97
98
    /**
99
     * {@inheritdoc}
100
     */
101
    public function appendSegmentsToPointer(string $pointer, string ...$segments): string
102
    {
103
        $segments = array_map([$this, 'escape'], $segments);
104
105
        return $pointer.'/'.implode('/', $segments);
106
    }
107
108
    /**
109
     * Traverses through the segments of the JSON pointer and returns the result.
110
     *
111
     * @return mixed
112
     *
113
     * @throws InvalidJsonPointerException when the JSON pointer does not exist
114
     */
115
    private function &traverseJson(string $pointer)
116
    {
117
        $json = &$this->json;
118
119
        $pointerSegments = $this->splitPointerIntoSegments($pointer);
120
        foreach ($pointerSegments as $pointerSegment) {
121
            $json = &$this->resolveReference($json);
122
123
            if (is_object($json) && property_exists($json, $pointerSegment)) {
124
                $json = &$json->{$pointerSegment};
125
126
                continue;
127
            }
128
129
            if (is_array($json) && array_key_exists($pointerSegment, $json)) {
130
                $json = &$json[$pointerSegment];
131
132
                continue;
133
            }
134
135
            throw new InvalidJsonPointerException(sprintf('The JSON pointer "%s" does not exist.', $pointer));
136
        }
137
138
        $json = &$this->resolveReference($json);
139
140
        return $json;
141
    }
142
143
    /**
144
     * Splits the JSON pointer into segments.
145
     *
146
     * @return string[]
147
     */
148
    private function splitPointerIntoSegments(string $pointer): array
149
    {
150
        $segments = array_slice(explode('/', $pointer), 1);
151
152
        return $this->unescape($segments);
153
    }
154
155
    /**
156
     * Unescapes the JSON pointer variants of the ~ and / characters.
157
     *
158
     * @param string|string[] $value
159
     *
160
     * @return string|string[]
161
     */
162
    private function unescape($value)
163
    {
164
        return str_replace(
165
            array_values(self::ESCAPE_CHARACTERS),
166
            array_keys(self::ESCAPE_CHARACTERS),
167
            $value
168
        );
169
    }
170
171
    /**
172
     * Resolves the reference when the provided JSON is a {@see Reference} instance.
173
     *
174
     * @param mixed $json
175
     *
176
     * @return mixed
177
     */
178
    private function &resolveReference(&$json)
179
    {
180
        if ($json instanceof Reference) {
181
            $jsonPointer = $this->withJson($json->getJsonSchema());
182
            $json = &$jsonPointer->getByReference($json->getPointer());
183
        }
184
185
        return $json;
186
    }
187
}
188