Completed
Pull Request — master (#17)
by Viacheslav
28:32
created

RefResolver::preProcessReferences()   C

Complexity

Conditions 14
Paths 12

Size

Total Lines 40
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 40
rs 5.0864
c 0
b 0
f 0
cc 14
eloc 22
nc 12
nop 3

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
    public function setResolutionScope($resolutionScope)
21
    {
22
        $rootResolver = $this->rootResolver ? $this->rootResolver : $this;
23
        if ($resolutionScope === $rootResolver->resolutionScope) {
24
            return $resolutionScope;
25
        }
26
        $prev = $rootResolver->resolutionScope;
27
        $rootResolver->resolutionScope = $resolutionScope;
28
        return $prev;
29
    }
30
31
    /**
32
     * @return string
33
     */
34
    public function getResolutionScope()
35
    {
36
        $rootResolver = $this->rootResolver ? $this->rootResolver : $this;
37
        return $rootResolver->resolutionScope;
38
    }
39
40
41
    public function updateResolutionScope($id)
42
    {
43
        $id = rtrim($id, '#');
44
        $rootResolver = $this->rootResolver ? $this->rootResolver : $this;
45
        if (strpos($id, '://') !== false) {
46
            $prev = $rootResolver->setResolutionScope($id);
47
        } else {
48
            $prev = $rootResolver->setResolutionScope(Helper::resolveURI($rootResolver->resolutionScope, $id));
49
        }
50
51
        return $prev;
52
    }
53
54
    public function setupResolutionScope($id, $data)
55
    {
56
        $rootResolver = $this->rootResolver ? $this->rootResolver : $this;
57
58
        $prev = $rootResolver->updateResolutionScope($id);
59
60
        $refParts = explode('#', $rootResolver->resolutionScope, 2);
61
62
        if ($refParts[0]) { // external uri
63
            $resolver = &$rootResolver->remoteRefResolvers[$refParts[0]];
64
            if ($resolver === null) {
65
                $resolver = new RefResolver();
66
                $resolver->rootResolver = $rootResolver;
67
                $resolver->url = $refParts[0];
68
                $this->remoteRefResolvers[$refParts[0]] = $resolver;
69
            }
70
        } else { // local uri
71
            $resolver = $this;
72
        }
73
74
        if (empty($refParts[1])) {
75
            $resolver->rootData = $data;
76
        } else {
77
            $refPath = '#' . $refParts[1];
78
            $resolver->refs[$refPath] = new Ref($refPath, $data);
79
        }
80
81
        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
    public function __construct($rootData = null)
100
    {
101
        $this->rootData = $rootData;
102
    }
103
104
    public function setRootData($rootData)
105
    {
106
        $this->rootData = $rootData;
107
        return $this;
108
    }
109
110
111
    public function setRemoteRefProvider(RemoteRefProvider $provider)
112
    {
113
        $this->refProvider = $provider;
114
        return $this;
115
    }
116
117
    private function getRefProvider()
118
    {
119
        if (null === $this->refProvider) {
120
            $this->refProvider = new BasicFetcher();
121
        }
122
        return $this->refProvider;
123
    }
124
125
    /**
126
     * @param string $referencePath
127
     * @return Ref
128
     * @throws InvalidValue
129
     */
130
    public function resolveReference($referencePath)
131
    {
132
        if ($this->resolutionScope) {
133
            $referencePath = Helper::resolveURI($this->resolutionScope, $referencePath);
134
        }
135
136
        $refParts = explode('#', $referencePath, 2);
137
        $url = rtrim($refParts[0], '#');
138
        $refLocalPath = isset($refParts[1]) ? '#' . $refParts[1] : '#';
139
140
        if ($url === $this->url) {
141
            $referencePath = $refLocalPath;
142
        }
143
144
        /** @var null|Ref $ref */
145
        $ref = &$this->refs[$referencePath];
146
147
        $refResolver = $this;
148
149
        if (null === $ref) {
150
            if ($referencePath[0] === '#') {
151
                if ($referencePath === '#') {
152
                    $ref = new Ref($referencePath, $refResolver->rootData);
153
                } else {
154
                    $ref = new Ref($referencePath);
155
                    $path = explode('/', trim($referencePath, '#/'));
156
157
                    /** @var JsonSchema $branch */
158
                    $branch = &$refResolver->rootData;
159
                    while (!empty($path)) {
160
                        if (isset($branch->{Schema::ID_D4}) && is_string($branch->{Schema::ID_D4})) {
161
                            $refResolver->updateResolutionScope($branch->{Schema::ID_D4});
162
                        }
163
                        if (isset($branch->{Schema::ID}) && is_string($branch->{Schema::ID})) {
164
                            $refResolver->updateResolutionScope($branch->{Schema::ID});
165
                        }
166
167
                        $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
                        $folder = str_replace(array('~0', '~1', '%25'), array('~', '/', '%'), $folder);
173
174
                        if ($branch instanceof \stdClass && isset($branch->$folder)) {
175
                            $branch = &$branch->$folder;
176
                        } elseif (is_array($branch) && isset($branch[$folder])) {
177
                            $branch = &$branch[$folder];
178
                        } else {
179
                            throw new InvalidValue('Could not resolve ' . $referencePath . '@' . $this->getResolutionScope() . ': ' . $folder);
180
                        }
181
                    }
182
                    $ref->setData($branch);
183
                }
184
            } else {
185
                if ($url !== $this->url) {
186
                    $rootResolver = $this->rootResolver ? $this->rootResolver : $this;
187
                    /** @var null|RefResolver $refResolver */
188
                    $refResolver = &$rootResolver->remoteRefResolvers[$url];
189
                    $this->setResolutionScope($url);
190
                    if (null === $refResolver) {
191
                        $rootData = $rootResolver->getRefProvider()->getSchemaData($url);
192
                        $refResolver = new RefResolver($rootData);
0 ignored issues
show
Documentation introduced by
$rootData is of type object<stdClass>|false, but the function expects a object<Swaggest\JsonSchema\JsonSchema>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
193
                        $refResolver->rootResolver = $rootResolver;
194
                        $refResolver->refProvider = $this->refProvider;
195
                        $refResolver->url = $url;
196
                        $rootResolver->setResolutionScope($url);
197
                    }
198
                }
199
200
                $ref = $refResolver->resolveReference($refLocalPath);
201
            }
202
        }
203
204
        return $ref;
205
    }
206
207
208
    /**
209
     * @param $data
210
     * @param Context $options
211
     * @param int $nestingLevel
212
     * @throws Exception
213
     */
214
    public function preProcessReferences($data, Context $options, $nestingLevel = 0)
215
    {
216
        if ($nestingLevel > 200) {
217
            throw new Exception('Too deep nesting level', Exception::DEEP_NESTING);
218
        }
219
        if (is_array($data)) {
220
            foreach ($data as $key => $item) {
221
                $this->preProcessReferences($item, $options, $nestingLevel + 1);
222
            }
223
        } elseif ($data instanceof \stdClass) {
224
            /** @var JsonSchema $data */
225
            if (
226
                isset($data->{Schema::ID_D4})
227
                && is_string($data->{Schema::ID_D4})
228
                && (($options->version === Schema::VERSION_AUTO) || $options->version === Schema::VERSION_DRAFT_04)
229
            ) {
230
                $prev = $this->setupResolutionScope($data->{Schema::ID_D4}, $data);
231
                /** @noinspection PhpUnusedLocalVariableInspection */
232
                $_ = new ScopeExit(function () use ($prev, $options) {
0 ignored issues
show
Unused Code introduced by
$_ is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
233
                    $this->setResolutionScope($prev);
234
                });
235
            }
236
237
            if (isset($data->{Schema::ID})
238
                && is_string($data->{Schema::ID})
239
                && (($options->version === Schema::VERSION_AUTO) || $options->version >= Schema::VERSION_DRAFT_06)
240
            ) {
241
                $prev = $this->setupResolutionScope($data->{Schema::ID}, $data);
242
                /** @noinspection PhpUnusedLocalVariableInspection */
243
                $_ = new ScopeExit(function () use ($prev, $options) {
0 ignored issues
show
Unused Code introduced by
$_ is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
244
                    $this->setResolutionScope($prev);
245
                });
246
            }
247
248
249
            foreach ((array)$data as $key => $value) {
250
                $this->preProcessReferences($value, $options, $nestingLevel + 1);
251
            }
252
        }
253
    }
254
255
256
}