Passed
Branch master (bd1396)
by Viacheslav
07:53
created

RefResolver::setupResolutionScope()   B

Complexity

Conditions 5
Paths 12

Size

Total Lines 28
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 28
c 0
b 0
f 0
ccs 17
cts 17
cp 1
rs 8.439
cc 5
eloc 18
nc 12
nop 2
crap 5
1
<?php
2
3
namespace Swaggest\JsonSchema;
4
5
use PhpLang\ScopeExit;
6
use Swaggest\JsonSchema\Constraint\Ref;
7
use Swaggest\JsonSchema\RemoteRef\BasicFetcher;
8
9
class RefResolver
10
{
11
    public $resolutionScope = '';
12
    public $url;
13
    /** @var RefResolver */
14
    private $rootResolver;
15
16
    /**
17
     * @param mixed $resolutionScope
18
     * @return string previous value
19
     */
20 572
    public function setResolutionScope($resolutionScope)
21
    {
22 572
        $rootResolver = $this->rootResolver ? $this->rootResolver : $this;
23 572
        if ($resolutionScope === $rootResolver->resolutionScope) {
24 572
            return $resolutionScope;
25
        }
26 442
        $prev = $rootResolver->resolutionScope;
27 442
        $rootResolver->resolutionScope = $resolutionScope;
28 442
        return $prev;
29
    }
30
31
    /**
32
     * @return string
33
     */
34 571
    public function getResolutionScope()
35
    {
36 571
        $rootResolver = $this->rootResolver ? $this->rootResolver : $this;
37 571
        return $rootResolver->resolutionScope;
38
    }
39
40
41 400
    public function updateResolutionScope($id)
42
    {
43 400
        $id = rtrim($id, '#');
44 400
        $rootResolver = $this->rootResolver ? $this->rootResolver : $this;
45 400
        if (strpos($id, '://') !== false) {
46 400
            $prev = $rootResolver->setResolutionScope($id);
47
        } else {
48 360
            $prev = $rootResolver->setResolutionScope(Helper::resolveURI($rootResolver->resolutionScope, $id));
49
        }
50
51 400
        return $prev;
52
    }
53
54 394
    public function setupResolutionScope($id, $data)
55
    {
56 394
        $rootResolver = $this->rootResolver ? $this->rootResolver : $this;
57
58 394
        $prev = $rootResolver->updateResolutionScope($id);
59
60 394
        $refParts = explode('#', $rootResolver->resolutionScope, 2);
61
62 394
        if ($refParts[0]) { // external uri
63 394
            $resolver = &$rootResolver->remoteRefResolvers[$refParts[0]];
64 394
            if ($resolver === null) {
65 394
                $resolver = new RefResolver();
66 394
                $resolver->rootResolver = $rootResolver;
67 394
                $resolver->url = $refParts[0];
68 394
                $this->remoteRefResolvers[$refParts[0]] = $resolver;
69
            }
70
        } else { // local uri
71 12
            $resolver = $this;
72
        }
73
74 394
        if (empty($refParts[1])) {
75 394
            $resolver->rootData = $data;
76
        } else {
77 12
            $refPath = '#' . $refParts[1];
78 12
            $resolver->refs[$refPath] = new Ref($refPath, $data);
79
        }
80
81 394
        return $prev;
82
    }
83
84
    private $rootData;
85
86
    /** @var Ref[] */
87
    private $refs = array();
88
89
    /** @var RefResolver[]|null[] */
90
    private $remoteRefResolvers = array();
91
92
    /** @var RemoteRefProvider */
93
    private $refProvider;
94
95
    /**
96
     * RefResolver constructor.
97
     * @param JsonSchema $rootData
98
     */
99 3139
    public function __construct($rootData = null)
100
    {
101 3139
        $this->rootData = $rootData;
102 3139
    }
103
104 1693
    public function setRootData($rootData)
105
    {
106 1693
        $this->rootData = $rootData;
107 1693
        return $this;
108
    }
109
110
111 3079
    public function setRemoteRefProvider(RemoteRefProvider $provider)
112
    {
113 3079
        $this->refProvider = $provider;
114 3079
        return $this;
115
    }
116
117 107
    private function getRefProvider()
118
    {
119 107
        if (null === $this->refProvider) {
120
            $this->refProvider = new BasicFetcher();
121
        }
122 107
        return $this->refProvider;
123
    }
124
125
    /**
126
     * @param string $referencePath
127
     * @return Ref
128
     * @throws InvalidValue
129
     */
130 383
    public function resolveReference($referencePath)
131
    {
132 383
        if ($this->resolutionScope) {
133 173
            $referencePath = Helper::resolveURI($this->resolutionScope, $referencePath);
134
        }
135
136 383
        $refParts = explode('#', $referencePath, 2);
137 383
        $url = rtrim($refParts[0], '#');
138 383
        $refLocalPath = isset($refParts[1]) ? '#' . $refParts[1] : '#';
139
140 383
        if ($url === $this->url) {
141
            $referencePath = $refLocalPath;
142
        }
143
144
        /** @var null|Ref $ref */
145 383
        $ref = &$this->refs[$referencePath];
146
147 383
        $refResolver = $this;
148
149 383
        if (null === $ref) {
150 377
            if ($referencePath[0] === '#') {
151 371
                if ($referencePath === '#') {
152 144
                    $ref = new Ref($referencePath, $refResolver->rootData);
153
                } else {
154 295
                    $ref = new Ref($referencePath);
155 295
                    $path = explode('/', trim($referencePath, '#/'));
156
157
                    /** @var JsonSchema $branch */
158 295
                    $branch = &$refResolver->rootData;
159 295
                    while (!empty($path)) {
160 295
                        if (isset($branch->{Schema::ID_D4}) && is_string($branch->{Schema::ID_D4})) {
161 19
                            $refResolver->updateResolutionScope($branch->{Schema::ID_D4});
162
                        }
163 295
                        if (isset($branch->{Schema::ID}) && is_string($branch->{Schema::ID})) {
164 75
                            $refResolver->updateResolutionScope($branch->{Schema::ID});
165
                        }
166
167 295
                        $folder = array_shift($path);
168
169
                        // unescaping special characters
170
                        // https://tools.ietf.org/html/draft-ietf-appsawg-json-pointer-07#section-4
171
                        // https://github.com/json-schema-org/JSON-Schema-Test-Suite/issues/130
172 295
                        $folder = str_replace(array('~0', '~1', '%25'), array('~', '/', '%'), $folder);
173
174 295
                        if ($branch instanceof \stdClass && isset($branch->$folder)) {
175 295
                            $branch = &$branch->$folder;
176 12
                        } elseif (is_array($branch) && isset($branch[$folder])) {
177 12
                            $branch = &$branch[$folder];
178
                        } else {
179
                            throw new InvalidValue('Could not resolve ' . $referencePath . '@' . $this->getResolutionScope() . ': ' . $folder);
180
                        }
181
                    }
182 371
                    $ref->setData($branch);
183
                }
184
            } else {
185 199
                if ($url !== $this->url) {
186 199
                    $rootResolver = $this->rootResolver ? $this->rootResolver : $this;
187
                    /** @var null|RefResolver $refResolver */
188 199
                    $refResolver = &$rootResolver->remoteRefResolvers[$url];
189 199
                    $this->setResolutionScope($url);
190 199
                    if (null === $refResolver) {
191 107
                        $rootData = $rootResolver->getRefProvider()->getSchemaData($url);
192 107
                        $refResolver = new RefResolver($rootData);
193 107
                        $refResolver->rootResolver = $rootResolver;
194 107
                        $refResolver->refProvider = $this->refProvider;
195 107
                        $refResolver->url = $url;
196 107
                        $rootResolver->setResolutionScope($url);
197
                    }
198
                }
199
200 199
                $ref = $refResolver->resolveReference($refLocalPath);
201
            }
202
        }
203
204 383
        return $ref;
205
    }
206
207
208
    /**
209
     * @param mixed $data
210
     * @param Context $options
211
     * @param int $nestingLevel
212
     * @throws Exception
213
     */
214 3138
    public function preProcessReferences($data, Context $options, $nestingLevel = 0)
215
    {
216 3138
        if ($nestingLevel > 200) {
217
            throw new Exception('Too deep nesting level', Exception::DEEP_NESTING);
218
        }
219 3138
        if (is_array($data)) {
220 1324
            foreach ($data as $key => $item) {
221 1324
                $this->preProcessReferences($item, $options, $nestingLevel + 1);
222
            }
223 3136
        } elseif ($data instanceof \stdClass) {
224
            /** @var JsonSchema $data */
225
            if (
226 3120
                isset($data->{Schema::ID_D4})
227 3120
                && is_string($data->{Schema::ID_D4})
228 3120
                && (($options->version === Schema::VERSION_AUTO) || $options->version === Schema::VERSION_DRAFT_04)
229
            ) {
230 348
                $prev = $this->setupResolutionScope($data->{Schema::ID_D4}, $data);
231
                /** @noinspection PhpUnusedLocalVariableInspection */
232
                $_ = new ScopeExit(function () use ($prev) {
0 ignored issues
show
Unused Code introduced by
The assignment to $_ is dead and can be removed.
Loading history...
233 348
                    $this->setResolutionScope($prev);
234 348
                });
235
            }
236
237 3120
            if (isset($data->{Schema::ID})
238 3120
                && is_string($data->{Schema::ID})
239 3120
                && (($options->version === Schema::VERSION_AUTO) || $options->version >= Schema::VERSION_DRAFT_06)
240
            ) {
241 365
                $prev = $this->setupResolutionScope($data->{Schema::ID}, $data);
242
                /** @noinspection PhpUnusedLocalVariableInspection */
243 365
                $_ = new ScopeExit(function () use ($prev) {
244 365
                    $this->setResolutionScope($prev);
245 365
                });
246
            }
247
248
249 3120
            foreach ((array)$data as $key => $value) {
250 3118
                $this->preProcessReferences($value, $options, $nestingLevel + 1);
251
            }
252
        }
253 3138
    }
254
255
256
}