Completed
Push — master ( eca355...06186e )
by Viacheslav
11s
created

RefResolver   B

Complexity

Total Complexity 52

Size/Duplication

Total Lines 245
Duplicated Lines 0 %

Test Coverage

Coverage 94.69%

Importance

Changes 0
Metric Value
wmc 52
dl 0
loc 245
ccs 107
cts 113
cp 0.9469
rs 7.9487
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A setResolutionScope() 0 9 3
A getResolutionScope() 0 4 2
B setupResolutionScope() 0 28 5
A __construct() 0 3 1
A updateResolutionScope() 0 11 3
C preProcessReferences() 0 37 14
A getRefProvider() 0 6 2
C resolveReference() 0 74 20
A setRemoteRefProvider() 0 4 1
A setRootData() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like RefResolver often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use RefResolver, and based on these observations, apply Extract Interface, too.

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 594
    public function setResolutionScope($resolutionScope)
22
    {
23 594
        $rootResolver = $this->rootResolver ? $this->rootResolver : $this;
24 594
        if ($resolutionScope === $rootResolver->resolutionScope) {
25 594
            return $resolutionScope;
26
        }
27 457
        $prev = $rootResolver->resolutionScope;
28 457
        $rootResolver->resolutionScope = $resolutionScope;
29 457
        return $prev;
30
    }
31
32
    /**
33
     * @return string
34
     */
35 593
    public function getResolutionScope()
36
    {
37 593
        $rootResolver = $this->rootResolver ? $this->rootResolver : $this;
38 593
        return $rootResolver->resolutionScope;
39
    }
40
41
42
    /**
43
     * @param string $id
44
     * @return string
45
     */
46 416
    public function updateResolutionScope($id)
47
    {
48 416
        $id = rtrim($id, '#'); // safe to trim because # in hashCode must be urlencoded to %23
49 416
        $rootResolver = $this->rootResolver ? $this->rootResolver : $this;
50 416
        if (strpos($id, '://') !== false) {
51 416
            $prev = $rootResolver->setResolutionScope($id);
52
        } else {
53 364
            $prev = $rootResolver->setResolutionScope(Helper::resolveURI($rootResolver->resolutionScope, $id));
54
        }
55
56 416
        return $prev;
57
    }
58
59 407
    public function setupResolutionScope($id, $data)
60
    {
61 407
        $rootResolver = $this->rootResolver ? $this->rootResolver : $this;
62
63 407
        $prev = $rootResolver->updateResolutionScope($id);
64
65 407
        $refParts = explode('#', $rootResolver->resolutionScope, 2);
66
67 407
        if ($refParts[0]) { // external uri
68 407
            $resolver = &$rootResolver->remoteRefResolvers[$refParts[0]];
69 407
            if ($resolver === null) {
70 407
                $resolver = new RefResolver();
71 407
                $resolver->rootResolver = $rootResolver;
72 407
                $resolver->url = $refParts[0];
73 407
                $this->remoteRefResolvers[$refParts[0]] = $resolver;
74
            }
75
        } else { // local uri
76 6
            $resolver = $this;
77
        }
78
79 407
        if (empty($refParts[1])) {
80 407
            $resolver->rootData = $data;
81
        } else {
82 12
            $refPath = '#' . $refParts[1];
83 12
            $resolver->refs[$refPath] = new Ref($refPath, $data);
84
        }
85
86 407
        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 3204
    public function __construct($rootData = null)
105
    {
106 3204
        $this->rootData = $rootData;
107 3204
    }
108
109 1732
    public function setRootData($rootData)
110
    {
111 1732
        $this->rootData = $rootData;
112 1732
        return $this;
113
    }
114
115
116 3138
    public function setRemoteRefProvider(RemoteRefProvider $provider)
117
    {
118 3138
        $this->refProvider = $provider;
119 3138
        return $this;
120
    }
121
122 122
    private function getRefProvider()
123
    {
124 122
        if (null === $this->refProvider) {
125
            $this->refProvider = new BasicFetcher();
126
        }
127 122
        return $this->refProvider;
128
    }
129
130
    /**
131
     * @param string $referencePath
132
     * @return Ref
133
     * @throws InvalidValue
134
     */
135 405
    public function resolveReference($referencePath)
136
    {
137 405
        if ($this->resolutionScope) {
138 186
            $referencePath = Helper::resolveURI($this->resolutionScope, $referencePath);
139
        }
140
141 405
        $refParts = explode('#', $referencePath, 2);
142 405
        $url = rtrim($refParts[0], '#');
143 405
        $refLocalPath = isset($refParts[1]) ? '#' . $refParts[1] : '#';
144
145 405
        if ($url === $this->url) {
146
            $referencePath = $refLocalPath;
147
        }
148
149
        /** @var null|Ref $ref */
150 405
        $ref = &$this->refs[$referencePath];
151
152 405
        $refResolver = $this;
153
154 405
        if (null === $ref) {
155 399
            if ($referencePath[0] === '#') {
156 393
                if ($referencePath === '#') {
157 156
                    $ref = new Ref($referencePath, $refResolver->rootData);
158
                } else {
159 312
                    $ref = new Ref($referencePath);
160
                    try {
161 312
                        $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 312
                    $branch = &$refResolver->rootData;
168 312
                    while (!empty($path)) {
169 312
                        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 312
                        if (isset($branch->{Schema::PROP_ID}) && is_string($branch->{Schema::PROP_ID})) {
173 75
                            $refResolver->updateResolutionScope($branch->{Schema::PROP_ID});
174
                        }
175
176 312
                        $folder = array_shift($path);
177
178 312
                        if ($branch instanceof \stdClass && isset($branch->$folder)) {
179 312
                            $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 393
                    $ref->setData($branch);
187
                }
188
            } else {
189 220
                if ($url !== $this->url) {
190 220
                    $rootResolver = $this->rootResolver ? $this->rootResolver : $this;
191
                    /** @var null|RefResolver $refResolver */
192 220
                    $refResolver = &$rootResolver->remoteRefResolvers[$url];
193 220
                    $this->setResolutionScope($url);
194 220
                    if (null === $refResolver) {
195 122
                        $rootData = $rootResolver->getRefProvider()->getSchemaData($url);
196 122
                        $refResolver = new RefResolver($rootData);
197 122
                        $refResolver->rootResolver = $rootResolver;
198 122
                        $refResolver->refProvider = $this->refProvider;
199 122
                        $refResolver->url = $url;
200 122
                        $rootResolver->setResolutionScope($url);
201
                    }
202
                }
203
204 220
                $ref = $refResolver->resolveReference($refLocalPath);
205
            }
206
        }
207
208 405
        return $ref;
209
    }
210
211
212
    /**
213
     * @param mixed $data
214
     * @param Context $options
215
     * @param int $nestingLevel
216
     * @throws Exception
217
     */
218 3203
    public function preProcessReferences($data, Context $options, $nestingLevel = 0)
219
    {
220 3203
        if ($nestingLevel > 200) {
221
            throw new Exception('Too deep nesting level', Exception::DEEP_NESTING);
222
        }
223 3203
        if (is_array($data)) {
224 1347
            foreach ($data as $key => $item) {
225 1347
                $this->preProcessReferences($item, $options, $nestingLevel + 1);
226
            }
227 3201
        } elseif ($data instanceof \stdClass) {
228
            /** @var JsonSchema $data */
229
            if (
230 3185
                isset($data->{Schema::PROP_ID_D4})
231 3185
                && is_string($data->{Schema::PROP_ID_D4})
232 3185
                && (($options->version === Schema::VERSION_AUTO) || $options->version === Schema::VERSION_DRAFT_04)
233
            ) {
234 361
                $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 361
                    $this->setResolutionScope($prev);
238 361
                });
239
            }
240
241 3185
            if (isset($data->{Schema::PROP_ID})
242 3185
                && is_string($data->{Schema::PROP_ID})
243 3185
                && (($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 3185
            foreach ((array)$data as $key => $value) {
254 3183
                $this->preProcessReferences($value, $options, $nestingLevel + 1);
255
            }
256
        }
257 3203
    }
258
259
260
}