Passed
Pull Request — master (#41)
by Viacheslav
02:56
created

RefResolver::setResolutionScope()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 9
c 0
b 0
f 0
ccs 7
cts 7
cp 1
rs 9.6666
cc 3
eloc 6
nc 4
nop 1
crap 3
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 585
    public function setResolutionScope($resolutionScope)
22
    {
23 585
        $rootResolver = $this->rootResolver ? $this->rootResolver : $this;
24 585
        if ($resolutionScope === $rootResolver->resolutionScope) {
25 585
            return $resolutionScope;
26
        }
27 450
        $prev = $rootResolver->resolutionScope;
28 450
        $rootResolver->resolutionScope = $resolutionScope;
29 450
        return $prev;
30
    }
31
32
    /**
33
     * @return string
34
     */
35 584
    public function getResolutionScope()
36
    {
37 584
        $rootResolver = $this->rootResolver ? $this->rootResolver : $this;
38 584
        return $rootResolver->resolutionScope;
39
    }
40
41
42
    /**
43
     * @param string $id
44
     * @return string
45
     */
46 407
    public function updateResolutionScope($id)
47
    {
48 407
        $id = rtrim($id, '#'); // safe to trim because # in hashCode must be urlencoded to %23
49 407
        $rootResolver = $this->rootResolver ? $this->rootResolver : $this;
50 407
        if (strpos($id, '://') !== false) {
51 407
            $prev = $rootResolver->setResolutionScope($id);
52
        } else {
53 360
            $prev = $rootResolver->setResolutionScope(Helper::resolveURI($rootResolver->resolutionScope, $id));
54
        }
55
56 407
        return $prev;
57
    }
58
59 400
    public function setupResolutionScope($id, $data)
60
    {
61 400
        $rootResolver = $this->rootResolver ? $this->rootResolver : $this;
62
63 400
        $prev = $rootResolver->updateResolutionScope($id);
64
65 400
        $refParts = explode('#', $rootResolver->resolutionScope, 2);
66
67 400
        if ($refParts[0]) { // external uri
68 400
            $resolver = &$rootResolver->remoteRefResolvers[$refParts[0]];
69 400
            if ($resolver === null) {
70 400
                $resolver = new RefResolver();
71 400
                $resolver->rootResolver = $rootResolver;
72 400
                $resolver->url = $refParts[0];
73 400
                $this->remoteRefResolvers[$refParts[0]] = $resolver;
74
            }
75
        } else { // local uri
76 6
            $resolver = $this;
77
        }
78
79 400
        if (empty($refParts[1])) {
80 400
            $resolver->rootData = $data;
81
        } else {
82 12
            $refPath = '#' . $refParts[1];
83 12
            $resolver->refs[$refPath] = new Ref($refPath, $data);
84
        }
85
86 400
        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 3159
    public function __construct($rootData = null)
105
    {
106 3159
        $this->rootData = $rootData;
107 3159
    }
108
109 1701
    public function setRootData($rootData)
110
    {
111 1701
        $this->rootData = $rootData;
112 1701
        return $this;
113
    }
114
115
116 3092
    public function setRemoteRefProvider(RemoteRefProvider $provider)
117
    {
118 3092
        $this->refProvider = $provider;
119 3092
        return $this;
120
    }
121
122 115
    private function getRefProvider()
123
    {
124 115
        if (null === $this->refProvider) {
125 1
            $this->refProvider = new BasicFetcher();
126
        }
127 115
        return $this->refProvider;
128
    }
129
130
    /**
131
     * @param string $referencePath
132
     * @return Ref
133
     * @throws InvalidValue
134
     */
135 396
    public function resolveReference($referencePath)
136
    {
137 396
        if ($this->resolutionScope) {
138 179
            $referencePath = Helper::resolveURI($this->resolutionScope, $referencePath);
139
        }
140
141 396
        $refParts = explode('#', $referencePath, 2);
142 396
        $url = rtrim($refParts[0], '#');
143 396
        $refLocalPath = isset($refParts[1]) ? '#' . $refParts[1] : '#';
144
145 396
        if ($url === $this->url) {
146
            $referencePath = $refLocalPath;
147
        }
148
149
        /** @var null|Ref $ref */
150 396
        $ref = &$this->refs[$referencePath];
151
152 396
        $refResolver = $this;
153
154 396
        if (null === $ref) {
155 390
            if ($referencePath[0] === '#') {
156 384
                if ($referencePath === '#') {
157 149
                    $ref = new Ref($referencePath, $refResolver->rootData);
158
                } else {
159 303
                    $ref = new Ref($referencePath);
160
                    try {
161 303
                        $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 303
                    $branch = &$refResolver->rootData;
168 303
                    while (!empty($path)) {
169 303
                        if (isset($branch->{Schema::PROP_ID_D4}) && is_string($branch->{Schema::PROP_ID_D4})) {
170 21
                            $refResolver->updateResolutionScope($branch->{Schema::PROP_ID_D4});
171
                        }
172 303
                        if (isset($branch->{Schema::PROP_ID}) && is_string($branch->{Schema::PROP_ID})) {
173 75
                            $refResolver->updateResolutionScope($branch->{Schema::PROP_ID});
174
                        }
175
176 303
                        $folder = array_shift($path);
177
178 303
                        if ($branch instanceof \stdClass && isset($branch->$folder)) {
179 303
                            $branch = &$branch->$folder;
180 12
                        } elseif (is_array($branch) && isset($branch[$folder])) {
181 12
                            $branch = &$branch[$folder];
182
                        } else {
183
                            throw new InvalidValue('Could not resolve ' . $referencePath . '@' . $this->getResolutionScope() . ': ' . $folder);
184
                        }
185
                    }
186 384
                    $ref->setData($branch);
187
                }
188
            } else {
189 213
                if ($url !== $this->url) {
190 213
                    $rootResolver = $this->rootResolver ? $this->rootResolver : $this;
191
                    /** @var null|RefResolver $refResolver */
192 213
                    $refResolver = &$rootResolver->remoteRefResolvers[$url];
193 213
                    $this->setResolutionScope($url);
194 213
                    if (null === $refResolver) {
195 115
                        $rootData = $rootResolver->getRefProvider()->getSchemaData($url);
196 115
                        $refResolver = new RefResolver($rootData);
197 115
                        $refResolver->rootResolver = $rootResolver;
198 115
                        $refResolver->refProvider = $this->refProvider;
199 115
                        $refResolver->url = $url;
200 115
                        $rootResolver->setResolutionScope($url);
201
                    }
202
                }
203
204 213
                $ref = $refResolver->resolveReference($refLocalPath);
205
            }
206
        }
207
208 396
        return $ref;
209
    }
210
211
212
    /**
213
     * @param mixed $data
214
     * @param Context $options
215
     * @param int $nestingLevel
216
     * @throws Exception
217
     */
218 3158
    public function preProcessReferences($data, Context $options, $nestingLevel = 0)
219
    {
220 3158
        if ($nestingLevel > 200) {
221
            throw new Exception('Too deep nesting level', Exception::DEEP_NESTING);
222
        }
223 3158
        if (is_array($data)) {
224 1341
            foreach ($data as $key => $item) {
225 1341
                $this->preProcessReferences($item, $options, $nestingLevel + 1);
226
            }
227 3156
        } elseif ($data instanceof \stdClass) {
228
            /** @var JsonSchema $data */
229
            if (
230 3140
                isset($data->{Schema::PROP_ID_D4})
231 3140
                && is_string($data->{Schema::PROP_ID_D4})
232 3140
                && (($options->version === Schema::VERSION_AUTO) || $options->version === Schema::VERSION_DRAFT_04)
233
            ) {
234 354
                $prev = $this->setupResolutionScope($data->{Schema::PROP_ID_D4}, $data);
235
                /** @noinspection PhpUnusedLocalVariableInspection */
236
                $_ = new ScopeExit(function () use ($prev) {
0 ignored issues
show
Unused Code introduced by
The assignment to $_ is dead and can be removed.
Loading history...
237 354
                    $this->setResolutionScope($prev);
238 354
                });
239
            }
240
241 3140
            if (isset($data->{Schema::PROP_ID})
242 3140
                && is_string($data->{Schema::PROP_ID})
243 3140
                && (($options->version === Schema::VERSION_AUTO) || $options->version >= Schema::VERSION_DRAFT_06)
244
            ) {
245 365
                $prev = $this->setupResolutionScope($data->{Schema::PROP_ID}, $data);
246
                /** @noinspection PhpUnusedLocalVariableInspection */
247 365
                $_ = new ScopeExit(function () use ($prev) {
248 365
                    $this->setResolutionScope($prev);
249 365
                });
250
            }
251
252
253 3140
            foreach ((array)$data as $key => $value) {
254 3138
                $this->preProcessReferences($value, $options, $nestingLevel + 1);
255
            }
256
        }
257 3158
    }
258
259
260
}