Completed
Push — master ( ccaae2...611d76 )
by Stéphane
7s
created

Uri::buildResolvedPathAgainst()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 12
ccs 6
cts 6
cp 1
rs 9.4285
cc 3
eloc 6
nc 3
nop 1
crap 3
1
<?php
2
3
/*
4
 * This file is part of the JVal package.
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
namespace JVal;
11
12
/**
13
 * Wraps a raw URI string, providing methods to deal with URI normalization,
14
 * comparison and resolution (including JSON pointers references).
15
 */
16
class Uri
17
{
18
    private static $partNames = [
19
        'scheme',
20
        'user',
21
        'pass',
22
        'host',
23
        'port',
24
        'path',
25
        'query',
26
        'fragment',
27
    ];
28
29
    /**
30
     * @var string
31
     */
32
    private $raw;
33
34
    /**
35
     * @var array
36
     */
37
    private $parts;
38
39
    /**
40
     * @var string
41
     */
42
    private $authority;
43
44
    /**
45
     * @var string[]
46
     */
47
    private $segments;
48
49
    /**
50
     * @var string
51
     */
52
    private $primaryIdentifier;
53
54
    /**
55
     * Constructor.
56
     *
57
     * @param string $rawUri
58
     */
59 371
    public function __construct($rawUri)
60
    {
61 371
        $this->buildFromRawUri($rawUri);
62 370
    }
63
64
    /**
65
     * @return string
66
     */
67 3
    public function getRawUri()
68
    {
69 3
        return $this->raw;
70
    }
71
72
    /**
73
     * @return string
74
     */
75 52
    public function getRawPointer()
76
    {
77 52
        return isset($this->parts['fragment']) ? $this->parts['fragment'] : '';
78
    }
79
80
    /**
81
     * @return bool
82
     */
83 85
    public function isAbsolute()
84
    {
85 85
        return $this->parts['scheme'] !== '';
86
    }
87
88
    /**
89
     * @return string
90
     */
91 61
    public function getScheme()
92
    {
93 61
        return $this->parts['scheme'];
94
    }
95
96
    /**
97
     * @return string
98
     */
99 61
    public function getAuthority()
100
    {
101 61
        return $this->authority;
102
    }
103
104
    /**
105
     * @return string
106
     */
107 61
    public function getPath()
108
    {
109 61
        return $this->parts['path'];
110
    }
111
112
    /**
113
     * @return string
114
     */
115 61
    public function getQuery()
116
    {
117 61
        return $this->parts['query'];
118
    }
119
120
    /**
121
     * @return string[]
122
     */
123 62
    public function getPointerSegments()
124
    {
125 62
        return $this->segments;
126
    }
127
128
    /**
129
     * Returns the primary resource identifier part of the URI, i.e. everything
130
     * excluding its fragment part.
131
     *
132
     * @return string
133
     */
134 341
    public function getPrimaryResourceIdentifier()
135
    {
136 341
        return $this->primaryIdentifier;
137
    }
138
139
    /**
140
     * Resolves the current (relative) URI against another (absolute) URI.
141
     * Example:.
142
     *
143
     * Current  = foo.json
144
     * Other    = http://localhost/bar/baz
145
     * Resolved = http://localhost/bar/foo.json
146
     *
147
     * @param Uri $uri
148
     *
149
     * @return string
150
     */
151 63
    public function resolveAgainst(Uri $uri)
152
    {
153 63
        if ($this->isAbsolute()) {
154 1
            throw new \LogicException(
155
                'Cannot resolve against another URI: URI is already absolute'
156 1
            );
157
        }
158
159 62
        if (!$uri->isAbsolute()) {
160 1
            throw new \LogicException(
161
                'Cannot resolve against another URI: reference URI is not absolute'
162 1
            );
163
        }
164
165 61
        $resolved = $this->buildResolvedUriAgainst($uri);
166 61
        $this->buildFromRawUri($resolved);
167
168 61
        return $resolved;
169
    }
170
171
    /**
172
     * Returns whether two URIs share the same primary resource identifier,
173
     * i.e. whether they point to the same document.
174
     *
175
     * @param Uri $uri
176
     *
177
     * @return bool
178
     */
179 5
    public function isSamePrimaryResource(Uri $uri)
180
    {
181 5
        if (!$this->isAbsolute() || !$uri->isAbsolute()) {
182 1
            throw new \LogicException('Cannot compare URIs: both must be absolute');
183
        }
184
185 4
        return $this->primaryIdentifier === $uri->getPrimaryResourceIdentifier();
186
    }
187
188 371
    private function buildFromRawUri($rawUri)
189
    {
190 371
        $this->raw = rawurldecode($rawUri);
191 371
        $this->parts = @parse_url($this->raw);
192
193 371
        if (false === $this->parts) {
194 1
            throw new \InvalidArgumentException("Cannot parse URI '{$rawUri}'");
195
        }
196
197 370
        foreach (self::$partNames as $part) {
198 370
            if (!isset($this->parts[$part])) {
199 370
                $this->parts[$part] = '';
200 370
            }
201 370
        }
202
203 370
        if ($this->parts['scheme'] === 'file' && preg_match('/^[A-Z]:/', $this->parts['path'])) {
204
            $this->parts['path'] = '/' . $this->parts['path'];
205
        }
206
207 370
        $this->authority = $this->buildAuthority();
208 370
        $this->segments = $this->buildSegments();
209 370
        $this->primaryIdentifier = $this->buildPrimaryIdentifier();
210 370
    }
211
212 370
    private function buildAuthority()
213
    {
214 370
        $userInfo = $this->parts['user'];
215 370
        $authority = $this->parts['host'];
216
217 370
        if ($this->parts['pass'] !== '') {
218 1
            $userInfo .= ':'.$this->parts['pass'];
219 1
        }
220
221 370
        if ($this->parts['port'] !== '') {
222 14
            $authority .= ':'.$this->parts['port'];
223 14
        }
224
225 370
        if ($userInfo !== '') {
226 1
            $authority = $userInfo.'@'.$authority;
227 1
        }
228
229 370
        return $authority;
230
    }
231
232 370
    private function buildSegments()
233
    {
234 370
        $segments = [];
235
236 370
        if (isset($this->parts['fragment'])) {
237 370
            $rawSegments = explode('/', $this->parts['fragment']);
238
239 370
            foreach ($rawSegments as $segment) {
240 370
                $segment = trim($segment);
241
242 370
                if ($segment !== '') {
243 56
                    $segment = str_replace('~1', '/', $segment);
244 56
                    $segment = str_replace('~0', '~', $segment);
245 56
                    $segments[] = $segment;
246 56
                }
247 370
            }
248 370
        }
249
250 370
        return $segments;
251
    }
252
253 370
    private function buildPrimaryIdentifier()
254
    {
255 370
        $identifier = '';
256
257 370
        if ($this->parts['scheme']) {
258 361
            $identifier .= $this->parts['scheme'].'://';
259 361
        }
260
261 370
        $identifier .= $this->authority.$this->parts['path'];
262
263 370
        if ($this->parts['query']) {
264 6
            $identifier .= '?'.$this->parts['query'];
265 6
        }
266
267 370
        return $identifier;
268
    }
269
270 61
    private function buildResolvedUriAgainst(Uri $uri)
271
    {
272 61
        $scheme = $uri->getScheme();
273 61
        $authority = $uri->getAuthority();
274 61
        $path = $uri->getPath();
275 61
        $query = $uri->getQuery();
276
277 61
        if ($this->getAuthority()) {
278 3
            $authority = $this->getAuthority();
279 3
            $path = $this->getPath();
280 3
            $query = $this->getQuery();
281 61
        } elseif ($this->getPath()) {
282 12
            $path = $this->buildResolvedPathAgainst($uri->getPath());
283 12
            $query = $this->getQuery();
284 58
        } elseif ($this->getQuery()) {
285 1
            $query = $this->getQuery();
286 1
        }
287
288 61
        return $this->appendRelativeParts(
289 61
            "{$scheme}://{$authority}{$path}",
290 61
            $query,
291 61
            $this->parts['fragment']
292 61
        );
293
    }
294
295 12
    private function buildResolvedPathAgainst($againstPath)
296
    {
297 12
        $ownPath = $this->getPath();
298
299 12
        if (0 !== strpos($ownPath, '/')) {
300 8
            $againstPath = $againstPath ?: '/';
301
302 8
            return preg_replace('#/([^/]*)$#', "/{$ownPath}", $againstPath);
303
        }
304
305 5
        return $ownPath;
306
    }
307
308 61
    private function appendRelativeParts($absolutePart, $query, $fragment)
309
    {
310 61
        if ($query) {
311 3
            $absolutePart .= '?'.$query;
312 3
        }
313
314 61
        if ($fragment) {
315 48
            $absolutePart .= '#'.$fragment;
316 48
        }
317
318 61
        return $absolutePart;
319
    }
320
}
321