1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Swaggest\JsonSchema; |
4
|
|
|
|
5
|
|
|
use PhpLang\ScopeExit; |
6
|
|
|
use Swaggest\JsonDiff\JsonPointer; |
7
|
|
|
use Swaggest\JsonSchema\Constraint\Ref; |
8
|
|
|
use Swaggest\JsonSchema\RemoteRef\BasicFetcher; |
9
|
|
|
|
10
|
|
|
class RefResolver |
11
|
|
|
{ |
12
|
|
|
public $resolutionScope = ''; |
13
|
|
|
public $url; |
14
|
|
|
/** @var RefResolver */ |
15
|
|
|
private $rootResolver; |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* @param mixed $resolutionScope |
19
|
|
|
* @return string previous value |
20
|
|
|
*/ |
21
|
614 |
|
public function setResolutionScope($resolutionScope) |
22
|
|
|
{ |
23
|
614 |
|
$rootResolver = $this->rootResolver ? $this->rootResolver : $this; |
24
|
614 |
|
if ($resolutionScope === $rootResolver->resolutionScope) { |
25
|
614 |
|
return $resolutionScope; |
26
|
|
|
} |
27
|
476 |
|
$prev = $rootResolver->resolutionScope; |
28
|
476 |
|
$rootResolver->resolutionScope = $resolutionScope; |
29
|
476 |
|
return $prev; |
30
|
|
|
} |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* @return string |
34
|
|
|
*/ |
35
|
613 |
|
public function getResolutionScope() |
36
|
|
|
{ |
37
|
613 |
|
$rootResolver = $this->rootResolver ? $this->rootResolver : $this; |
38
|
613 |
|
return $rootResolver->resolutionScope; |
39
|
|
|
} |
40
|
|
|
|
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* @param string $id |
44
|
|
|
* @return string |
45
|
|
|
*/ |
46
|
430 |
|
public function updateResolutionScope($id) |
47
|
|
|
{ |
48
|
430 |
|
$id = rtrim($id, '#'); // safe to trim because # in hashCode must be urlencoded to %23 |
49
|
430 |
|
$rootResolver = $this->rootResolver ? $this->rootResolver : $this; |
50
|
430 |
|
if (strpos($id, '://') !== false) { |
51
|
430 |
|
$prev = $rootResolver->setResolutionScope($id); |
52
|
|
|
} else { |
53
|
378 |
|
$prev = $rootResolver->setResolutionScope(Helper::resolveURI($rootResolver->resolutionScope, $id)); |
54
|
|
|
} |
55
|
|
|
|
56
|
430 |
|
return $prev; |
57
|
|
|
} |
58
|
|
|
|
59
|
421 |
|
public function setupResolutionScope($id, $data) |
60
|
|
|
{ |
61
|
421 |
|
$rootResolver = $this->rootResolver ? $this->rootResolver : $this; |
62
|
|
|
|
63
|
421 |
|
$prev = $rootResolver->updateResolutionScope($id); |
64
|
|
|
|
65
|
421 |
|
$refParts = explode('#', $rootResolver->resolutionScope, 2); |
66
|
|
|
|
67
|
421 |
|
if ($refParts[0]) { // external uri |
68
|
421 |
|
$resolver = &$rootResolver->remoteRefResolvers[$refParts[0]]; |
69
|
421 |
|
if ($resolver === null) { |
70
|
421 |
|
$resolver = new RefResolver(); |
71
|
421 |
|
$resolver->rootResolver = $rootResolver; |
72
|
421 |
|
$resolver->url = $refParts[0]; |
73
|
421 |
|
$this->remoteRefResolvers[$refParts[0]] = $resolver; |
74
|
|
|
} |
75
|
|
|
} else { // local uri |
76
|
6 |
|
$resolver = $this; |
77
|
|
|
} |
78
|
|
|
|
79
|
421 |
|
if (empty($refParts[1])) { |
80
|
421 |
|
$resolver->rootData = $data; |
81
|
|
|
} else { |
82
|
12 |
|
$refPath = '#' . $refParts[1]; |
83
|
12 |
|
$resolver->refs[$refPath] = new Ref($refPath, $data); |
84
|
|
|
} |
85
|
|
|
|
86
|
421 |
|
return $prev; |
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
private $rootData; |
90
|
|
|
|
91
|
|
|
/** @var Ref[] */ |
92
|
|
|
private $refs = array(); |
93
|
|
|
|
94
|
|
|
/** @var RefResolver[]|null[] */ |
95
|
|
|
private $remoteRefResolvers = array(); |
96
|
|
|
|
97
|
|
|
/** @var RemoteRefProvider */ |
98
|
|
|
private $refProvider; |
99
|
|
|
|
100
|
|
|
/** |
101
|
|
|
* RefResolver constructor. |
102
|
|
|
* @param JsonSchema $rootData |
103
|
|
|
*/ |
104
|
3265 |
|
public function __construct($rootData = null) |
105
|
|
|
{ |
106
|
3265 |
|
$this->rootData = $rootData; |
107
|
3265 |
|
} |
108
|
|
|
|
109
|
1766 |
|
public function setRootData($rootData) |
110
|
|
|
{ |
111
|
1766 |
|
$this->rootData = $rootData; |
112
|
1766 |
|
return $this; |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
|
116
|
3192 |
|
public function setRemoteRefProvider(RemoteRefProvider $provider) |
117
|
|
|
{ |
118
|
3192 |
|
$this->refProvider = $provider; |
119
|
3192 |
|
return $this; |
120
|
|
|
} |
121
|
|
|
|
122
|
127 |
|
private function getRefProvider() |
123
|
|
|
{ |
124
|
127 |
|
if (null === $this->refProvider) { |
125
|
5 |
|
$this->refProvider = new BasicFetcher(); |
126
|
|
|
} |
127
|
127 |
|
return $this->refProvider; |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
/** |
131
|
|
|
* @param string $referencePath |
132
|
|
|
* @return Ref |
133
|
|
|
* @throws InvalidValue |
134
|
|
|
*/ |
135
|
411 |
|
public function resolveReference($referencePath) |
136
|
|
|
{ |
137
|
411 |
|
if ($this->resolutionScope) { |
138
|
186 |
|
$referencePath = Helper::resolveURI($this->resolutionScope, $referencePath); |
139
|
|
|
} |
140
|
|
|
|
141
|
411 |
|
$refParts = explode('#', $referencePath, 2); |
142
|
411 |
|
$url = rtrim($refParts[0], '#'); |
143
|
411 |
|
$refLocalPath = isset($refParts[1]) ? '#' . $refParts[1] : '#'; |
144
|
|
|
|
145
|
411 |
|
if ($url === $this->url) { |
146
|
|
|
$referencePath = $refLocalPath; |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
/** @var null|Ref $ref */ |
150
|
411 |
|
$ref = &$this->refs[$referencePath]; |
151
|
|
|
|
152
|
411 |
|
$refResolver = $this; |
153
|
|
|
|
154
|
411 |
|
if (null === $ref) { |
155
|
405 |
|
if ($referencePath[0] === '#') { |
156
|
399 |
|
if ($referencePath === '#') { |
157
|
156 |
|
$ref = new Ref($referencePath, $refResolver->rootData); |
158
|
|
|
} else { |
159
|
318 |
|
$ref = new Ref($referencePath); |
160
|
|
|
try { |
161
|
318 |
|
$path = JsonPointer::splitPath($referencePath); |
162
|
|
|
} catch (\Swaggest\JsonDiff\Exception $e) { |
163
|
|
|
throw new InvalidValue('Invalid reference: ' . $referencePath . ', ' . $e->getMessage()); |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
/** @var JsonSchema $branch */ |
167
|
318 |
|
$branch = &$refResolver->rootData; |
168
|
318 |
|
while (!empty($path)) { |
169
|
318 |
|
if (isset($branch->{Schema::PROP_ID_D4}) && is_string($branch->{Schema::PROP_ID_D4})) { |
170
|
27 |
|
$refResolver->updateResolutionScope($branch->{Schema::PROP_ID_D4}); |
171
|
|
|
} |
172
|
318 |
|
if (isset($branch->{Schema::PROP_ID}) && is_string($branch->{Schema::PROP_ID})) { |
173
|
75 |
|
$refResolver->updateResolutionScope($branch->{Schema::PROP_ID}); |
174
|
|
|
} |
175
|
|
|
|
176
|
318 |
|
$folder = array_shift($path); |
177
|
318 |
|
if ($folder === ''){ |
|
|
|
|
178
|
|
|
// root element |
179
|
318 |
|
} elseif ($branch instanceof \stdClass && isset($branch->$folder)) { |
180
|
318 |
|
$branch = &$branch->$folder; |
181
|
12 |
|
} elseif (is_array($branch) && isset($branch[$folder])) { |
182
|
12 |
|
$branch = &$branch[$folder]; |
183
|
|
|
} else { |
184
|
|
|
throw new InvalidValue('Could not resolve ' . $referencePath . '@' . $this->getResolutionScope() . ': ' . $folder); |
185
|
|
|
} |
186
|
|
|
} |
187
|
399 |
|
$ref->setData($branch); |
188
|
|
|
} |
189
|
|
|
} else { |
190
|
225 |
|
if ($url !== $this->url) { |
191
|
225 |
|
$rootResolver = $this->rootResolver ? $this->rootResolver : $this; |
192
|
|
|
/** @var null|RefResolver $refResolver */ |
193
|
225 |
|
$refResolver = &$rootResolver->remoteRefResolvers[$url]; |
194
|
225 |
|
$this->setResolutionScope($url); |
195
|
225 |
|
if (null === $refResolver) { |
196
|
127 |
|
$rootData = $rootResolver->getRefProvider()->getSchemaData($url); |
197
|
127 |
|
$refResolver = new RefResolver($rootData); |
198
|
127 |
|
$refResolver->rootResolver = $rootResolver; |
199
|
127 |
|
$refResolver->refProvider = $this->refProvider; |
200
|
127 |
|
$refResolver->url = $url; |
201
|
127 |
|
$rootResolver->setResolutionScope($url); |
202
|
|
|
} |
203
|
|
|
} |
204
|
|
|
|
205
|
225 |
|
$ref = $refResolver->resolveReference($refLocalPath); |
206
|
|
|
} |
207
|
|
|
} |
208
|
|
|
|
209
|
411 |
|
return $ref; |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
|
213
|
|
|
/** |
214
|
|
|
* @param mixed $data |
215
|
|
|
* @param Context $options |
216
|
|
|
* @param int $nestingLevel |
217
|
|
|
* @throws Exception |
218
|
|
|
*/ |
219
|
3264 |
|
public function preProcessReferences($data, Context $options, $nestingLevel = 0) |
220
|
|
|
{ |
221
|
3264 |
|
if ($nestingLevel > 200) { |
222
|
|
|
throw new Exception('Too deep nesting level', Exception::DEEP_NESTING); |
223
|
|
|
} |
224
|
3264 |
|
if (is_array($data)) { |
225
|
1361 |
|
foreach ($data as $key => $item) { |
226
|
1361 |
|
$this->preProcessReferences($item, $options, $nestingLevel + 1); |
227
|
|
|
} |
228
|
3262 |
|
} elseif ($data instanceof \stdClass) { |
229
|
|
|
/** @var JsonSchema $data */ |
230
|
|
|
if ( |
231
|
3246 |
|
isset($data->{Schema::PROP_ID_D4}) |
232
|
3246 |
|
&& is_string($data->{Schema::PROP_ID_D4}) |
233
|
3246 |
|
&& (($options->version === Schema::VERSION_AUTO) || $options->version === Schema::VERSION_DRAFT_04) |
234
|
|
|
) { |
235
|
375 |
|
$prev = $this->setupResolutionScope($data->{Schema::PROP_ID_D4}, $data); |
236
|
|
|
/** @noinspection PhpUnusedLocalVariableInspection */ |
237
|
|
|
$_ = new ScopeExit(function () use ($prev) { |
|
|
|
|
238
|
375 |
|
$this->setResolutionScope($prev); |
239
|
375 |
|
}); |
240
|
|
|
} |
241
|
|
|
|
242
|
3246 |
|
if (isset($data->{Schema::PROP_ID}) |
243
|
3246 |
|
&& is_string($data->{Schema::PROP_ID}) |
244
|
3246 |
|
&& (($options->version === Schema::VERSION_AUTO) || $options->version >= Schema::VERSION_DRAFT_06) |
245
|
|
|
) { |
246
|
379 |
|
$prev = $this->setupResolutionScope($data->{Schema::PROP_ID}, $data); |
247
|
|
|
/** @noinspection PhpUnusedLocalVariableInspection */ |
248
|
379 |
|
$_ = new ScopeExit(function () use ($prev) { |
249
|
379 |
|
$this->setResolutionScope($prev); |
250
|
379 |
|
}); |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
|
254
|
3246 |
|
foreach ((array)$data as $key => $value) { |
255
|
3244 |
|
$this->preProcessReferences($value, $options, $nestingLevel + 1); |
256
|
|
|
} |
257
|
|
|
} |
258
|
3264 |
|
} |
259
|
|
|
|
260
|
|
|
|
261
|
|
|
} |
This check looks for the bodies of
if
statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.These
if
bodies can be removed. If you have an empty if but statements in theelse
branch, consider inverting the condition.could be turned into
This is much more concise to read.