Completed
Push — master ( 88285b...3df05c )
by John
03:08
created

RefResolver::unresolve()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
c 3
b 1
f 0
dl 0
loc 6
rs 9.4285
cc 1
eloc 3
nc 1
nop 0
1
<?php
2
/*
3
 * This file is part of the KleijnWeb\SwaggerBundle package.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
9
namespace KleijnWeb\SwaggerBundle\Document;
10
11
use KleijnWeb\SwaggerBundle\Document\Exception\ResourceNotReadableException;
12
use KleijnWeb\SwaggerBundle\Document\Exception\InvalidReferenceException;
13
14
/**
15
 * @author John Kleijn <[email protected]>
16
 */
17
class RefResolver
18
{
19
    /**
20
     * @var object
21
     */
22
    private $definition;
23
24
    /**
25
     * @var string
26
     */
27
    private $uri;
28
29
    /**
30
     * @var string
31
     */
32
    private $directory;
33
34
    /**
35
     * @var YamlParser
36
     */
37
    private $loader;
38
39
    /**
40
     * @param object $definition
41
     * @param string $uri
42
     * @param Loader $loader
43
     */
44
    public function __construct($definition, $uri, Loader $loader = null)
45
    {
46
        $this->definition = $definition;
47
        $this->uri        = $uri;
48
        $this->directory  = dirname($this->uri);
49
        $this->loader     = $loader ?: new Loader();
0 ignored issues
show
Documentation Bug introduced by
It seems like $loader ?: new \KleijnWe...undle\Document\Loader() of type object<KleijnWeb\SwaggerBundle\Document\Loader> is incompatible with the declared type object<KleijnWeb\Swagger...le\Document\YamlParser> of property $loader.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
50
    }
51
52
    /**
53
     * @return object
54
     */
55
    public function getDefinition()
56
    {
57
        return $this->definition;
58
    }
59
60
    /**
61
     * Resolve all references
62
     *
63
     * @return object
64
     */
65
    public function resolve()
66
    {
67
        $this->resolveRecursively($this->definition);
68
69
        return $this->definition;
70
    }
71
72
    /**
73
     * Revert to original state
74
     *
75
     * @return object
76
     */
77
    public function unresolve()
78
    {
79
        $this->unresolveRecursively($this->definition);
80
81
        return $this->definition;
82
    }
83
84
    /**
85
     * @param object|array $current
86
     * @param object       $document
87
     * @param string       $uri
88
     *
89
     * @throws InvalidReferenceException
90
     * @throws ResourceNotReadableException
91
     */
92
    private function resolveRecursively(&$current, $document = null, $uri = null)
93
    {
94
        $document = $document ?: $this->definition;
95
        $uri      = $uri ?: $this->uri;
96
97
        if (is_array($current)) {
98
            foreach ($current as &$value) {
99
                $this->resolveRecursively($value, $document, $uri);
100
            }
101
        } elseif (is_object($current)) {
102
            if (property_exists($current, '$ref')) {
103
                $uri = $current->{'$ref'};
104
                if ('#' === $uri[0]) {
105
                    $current = $this->lookup($uri, $document);
106
                    $this->resolveRecursively($current, $document, $uri);
107
                } else {
108
                    $uriSegs          = $this->parseUri($uri);
109
                    $normalizedUri    = $this->normalizeFileUri($uriSegs);
110
                    $externalDocument = $this->loadExternal($normalizedUri);
111
                    $current          = $this->lookup($uriSegs['fragment'], $externalDocument, $normalizedUri);
112
                    $this->resolveRecursively($current, $externalDocument, $normalizedUri);
113
                }
114
                if (is_object($current)) {
115
                    $current->{'x-ref-id'} = $uri;
116
                }
117
118
                return;
119
            }
120
            foreach ($current as $propertyName => &$propertyValue) {
121
                $this->resolveRecursively($propertyValue, $document, $uri);
122
            }
123
        }
124
    }
125
126
    /**
127
     * @param object|array $current
128
     * @param object|array $parent
129
     *
130
     * @return void
131
     */
132
    private function unresolveRecursively(&$current, &$parent = null)
133
    {
134
        foreach ($current as $key => &$value) {
135
            if ($value !== null && !is_scalar($value)) {
136
                $this->unresolveRecursively($value, $current);
137
            }
138
            if ($key === 'x-ref-id') {
139
                $parent = (object)['$ref' => $value];
140
            }
141
        }
142
    }
143
144
    /**
145
     * @param string $path
146
     * @param object $document
147
     * @param string $uri
148
     *
149
     * @return mixed
150
     * @throws InvalidReferenceException
151
     */
152
    private function lookup($path, $document, $uri = null)
153
    {
154
        $target = $this->lookupRecursively(
155
            explode('/', trim($path, '/#')),
156
            $document
157
        );
158
        if (!$target) {
159
            throw new InvalidReferenceException(
160
                "Target '$path' does not exist'" . ($uri ? " at '$uri''" : '')
161
            );
162
        }
163
164
        return $target;
165
    }
166
167
    /**
168
     * @param array  $segments
169
     * @param object $context
170
     *
171
     * @return mixed
172
     */
173
    private function lookupRecursively(array $segments, $context)
174
    {
175
        $segment = str_replace(['~0', '~1'], ['~', '/'], array_shift($segments));
176
        if (property_exists($context, $segment)) {
177
            if (!count($segments)) {
178
                return $context->$segment;
179
            }
180
181
            return $this->lookupRecursively($segments, $context->$segment);
182
        }
183
184
        return null;
185
    }
186
187
    /**
188
     * @param string $fileUrl
189
     *
190
     * @return object
191
     */
192
    private function loadExternal($fileUrl)
193
    {
194
        return $this->loader->load($fileUrl);
0 ignored issues
show
Bug introduced by
The method load() does not seem to exist on object<KleijnWeb\Swagger...le\Document\YamlParser>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
195
    }
196
197
    /**
198
     * @param array $uriSegs
199
     *
200
     * @return string
201
     */
202
    private function normalizeFileUri(array $uriSegs)
203
    {
204
        $path  = $uriSegs['path'];
205
        $auth  = !$uriSegs['user'] ? '' : "{$uriSegs['user']}:{$uriSegs['pass']}@";
206
        $query = !$uriSegs['query'] ? '' : "?{$uriSegs['query']}";
207
        $port  = !$uriSegs['port'] ? '' : ":{$uriSegs['port']}";
208
        $host  = !$uriSegs['host'] ? '' : "{$uriSegs['scheme']}://$auth{$uriSegs['host']}{$port}";
209
210
        if (substr($path, 0, 1) !== '/') {
211
            $path = "$this->directory/$path";
212
        }
213
214
        return "{$host}{$path}{$query}";
215
    }
216
217
    /**
218
     * @param string $uri
219
     *
220
     * @return array
221
     */
222
    private function parseUri($uri)
223
    {
224
        $defaults = [
225
            'scheme'   => '',
226
            'host'     => '',
227
            'port'     => '',
228
            'user'     => '',
229
            'pass'     => '',
230
            'path'     => '',
231
            'query'    => '',
232
            'fragment' => ''
233
        ];
234
235
        if (0 === strpos($uri, 'file://')) {
236
            // parse_url botches this up
237
            preg_match('@file://(?P<path>[^#]*)(?P<fragment>#.*)?@', $uri, $matches);
238
239
            return array_merge($defaults, array_intersect_key($matches, $defaults));
240
        }
241
242
        return array_merge($defaults, array_intersect_key(parse_url($uri), $defaults));
243
    }
244
}
245