Completed
Push — master ( e33e66...44de45 )
by Tobias
03:12
created

Uri::getPath()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Nyholm\Psr7;
6
7
use Psr\Http\Message\UriInterface;
8
9
/**
10
 * PSR-7 URI implementation.
11
 *
12
 * @author Michael Dowling
13
 * @author Tobias Schultze
14
 * @author Matthew Weier O'Phinney
15
 * @author Tobias Nyholm <[email protected]>
16
 */
17
class Uri implements UriInterface
18
{
19
    private static $schemes = [
20
        'http' => 80,
21
        'https' => 443,
22
    ];
23
24
    private static $charUnreserved = 'a-zA-Z0-9_\-\.~';
25
26
    private static $charSubDelims = '!\$&\'\(\)\*\+,;=';
27
28
    /** @var string Uri scheme. */
29
    private $scheme = '';
30
31
    /** @var string Uri user info. */
32
    private $userInfo = '';
33
34
    /** @var string Uri host. */
35
    private $host = '';
36
37
    /** @var int|null Uri port. */
38
    private $port;
39
40
    /** @var string Uri path. */
41
    private $path = '';
42
43
    /** @var string Uri query string. */
44
    private $query = '';
45
46
    /** @var string Uri fragment. */
47
    private $fragment = '';
48
49
    /**
50
     * @param string $uri
51
     */
52 123
    public function __construct($uri = '')
53
    {
54 123
        if ('' != $uri) {
55 82
            $parts = parse_url($uri);
56 82
            if (false === $parts) {
57 4
                throw new \InvalidArgumentException("Unable to parse URI: $uri");
58
            }
59
60 78
            $this->applyParts($parts);
61
        }
62 119
    }
63
64 63
    public function __toString(): string
65
    {
66 63
        return self::createUriString(
67 63
            $this->scheme,
68 63
            $this->getAuthority(),
69 63
            $this->path,
70 63
            $this->query,
71 63
            $this->fragment
72
        );
73
    }
74
75 30
    public function getScheme(): string
76
    {
77 30
        return $this->scheme;
78
    }
79
80 69
    public function getAuthority(): string
81
    {
82 69
        if ('' == $this->host) {
83 29
            return '';
84
        }
85
86 42
        $authority = $this->host;
87 42
        if ('' != $this->userInfo) {
88 5
            $authority = $this->userInfo.'@'.$authority;
89
        }
90
91 42
        if (null !== $this->port) {
92 5
            $authority .= ':'.$this->port;
93
        }
94
95 42
        return $authority;
96
    }
97
98 7
    public function getUserInfo(): string
99
    {
100 7
        return $this->userInfo;
101
    }
102
103 69
    public function getHost(): string
104
    {
105 69
        return $this->host;
106
    }
107
108 42
    public function getPort()
109
    {
110 42
        return $this->port;
111
    }
112
113 17
    public function getPath(): string
114
    {
115 17
        return $this->path;
116
    }
117
118 14
    public function getQuery(): string
119
    {
120 14
        return $this->query;
121
    }
122
123 13
    public function getFragment(): string
124
    {
125 13
        return $this->fragment;
126
    }
127
128 29
    public function withScheme($scheme): self
129
    {
130 29
        $scheme = $this->filterScheme($scheme);
131
132 28
        if ($this->scheme === $scheme) {
133
            return $this;
134
        }
135
136 28
        $new = clone $this;
137 28
        $new->scheme = $scheme;
138 28
        $new->port = $new->filterPort($new->port);
139
140 28
        return $new;
141
    }
142
143 4
    public function withUserInfo($user, $password = null): self
144
    {
145 4
        $info = $user;
146 4
        if ('' != $password) {
147 4
            $info .= ':'.$password;
148
        }
149
150 4
        if ($this->userInfo === $info) {
151
            return $this;
152
        }
153
154 4
        $new = clone $this;
155 4
        $new->userInfo = $info;
156
157 4
        return $new;
158
    }
159
160 13
    public function withHost($host): self
161
    {
162 13
        $host = $this->filterHost($host);
163
164 12
        if ($this->host === $host) {
165
            return $this;
166
        }
167
168 12
        $new = clone $this;
169 12
        $new->host = $host;
170
171 12
        return $new;
172
    }
173
174 8
    public function withPort($port): self
175
    {
176 8
        $port = $this->filterPort($port);
177
178 6
        if ($this->port === $port) {
179 1
            return $this;
180
        }
181
182 5
        $new = clone $this;
183 5
        $new->port = $port;
184
185 5
        return $new;
186
    }
187
188 15
    public function withPath($path): self
189
    {
190 15
        $path = $this->filterPath($path);
191
192 14
        if ($this->path === $path) {
193
            return $this;
194
        }
195
196 14
        $new = clone $this;
197 14
        $new->path = $path;
198
199 14
        return $new;
200
    }
201
202 12
    public function withQuery($query): self
203
    {
204 12
        $query = $this->filterQueryAndFragment($query);
205
206 11
        if ($this->query === $query) {
207
            return $this;
208
        }
209
210 11
        $new = clone $this;
211 11
        $new->query = $query;
212
213 11
        return $new;
214
    }
215
216 5
    public function withFragment($fragment): self
217
    {
218 5
        $fragment = $this->filterQueryAndFragment($fragment);
219
220 4
        if ($this->fragment === $fragment) {
221
            return $this;
222
        }
223
224 4
        $new = clone $this;
225 4
        $new->fragment = $fragment;
226
227 4
        return $new;
228
    }
229
230
    /**
231
     * Apply parse_url parts to a URI.
232
     *
233
     * @param array $parts Array of parse_url parts to apply
234
     */
235 78
    private function applyParts(array $parts)
236
    {
237 78
        $this->scheme = isset($parts['scheme']) ? $this->filterScheme($parts['scheme']) : '';
238 78
        $this->userInfo = isset($parts['user']) ? $parts['user'] : '';
239 78
        $this->host = isset($parts['host']) ? $this->filterHost($parts['host']) : '';
240 78
        $this->port = isset($parts['port']) ? $this->filterPort($parts['port']) : null;
241 78
        $this->path = isset($parts['path']) ? $this->filterPath($parts['path']) : '';
242 78
        $this->query = isset($parts['query']) ? $this->filterQueryAndFragment($parts['query']) : '';
243 78
        $this->fragment = isset($parts['fragment']) ? $this->filterQueryAndFragment($parts['fragment']) : '';
244 78
        if (isset($parts['pass'])) {
245 4
            $this->userInfo .= ':'.$parts['pass'];
246
        }
247 78
    }
248
249
    /**
250
     * Create a URI string from its various parts.
251
     *
252
     * @param string $scheme
253
     * @param string $authority
254
     * @param string $path
255
     * @param string $query
256
     * @param string $fragment
257
     *
258
     * @return string
259
     */
260 63
    private static function createUriString($scheme, $authority, $path, $query, $fragment): string
261
    {
262 63
        $uri = '';
263 63
        if ('' != $scheme) {
264 38
            $uri .= $scheme.':';
265
        }
266
267 63
        if ('' != $authority) {
268 38
            $uri .= '//'.$authority;
269
        }
270
271 63
        if ('' != $path) {
272 51
            if ('/' !== $path[0]) {
273 7
                if ('' != $authority) {
274
                    // If the path is rootless and an authority is present, the path MUST be prefixed by "/"
275 7
                    $path = '/'.$path;
276
                }
277 44
            } elseif (isset($path[1]) && '/' === $path[1]) {
278 1
                if ('' == $authority) {
279
                    // If the path is starting with more than one "/" and no authority is present, the
280
                    // starting slashes MUST be reduced to one.
281 1
                    $path = '/'.ltrim($path, '/');
282
                }
283
            }
284
285 51
            $uri .= $path;
286
        }
287
288 63
        if ('' != $query) {
289 33
            $uri .= '?'.$query;
290
        }
291
292 63
        if ('' != $fragment) {
293 14
            $uri .= '#'.$fragment;
294
        }
295
296 63
        return $uri;
297
    }
298
299
    /**
300
     * Is a given port non-standard for the current scheme?
301
     *
302
     * @param string $scheme
303
     * @param int    $port
304
     *
305
     * @return bool
306
     */
307 11
    private static function isNonStandardPort($scheme, $port): bool
308
    {
309 11
        return !isset(self::$schemes[$scheme]) || $port !== self::$schemes[$scheme];
310
    }
311
312
    /**
313
     * @param string $scheme
314
     *
315
     * @throws \InvalidArgumentException If the scheme is invalid
316
     *
317
     * @return string
318
     */
319 70
    private function filterScheme($scheme): string
320
    {
321 70
        if (!is_string($scheme)) {
322 1
            throw new \InvalidArgumentException('Scheme must be a string');
323
        }
324
325 69
        return strtolower($scheme);
326
    }
327
328
    /**
329
     * @param string $host
330
     *
331
     * @throws \InvalidArgumentException If the host is invalid
332
     *
333
     * @return string
334
     */
335 57
    private function filterHost($host): string
336
    {
337 57
        if (!is_string($host)) {
338 1
            throw new \InvalidArgumentException('Host must be a string');
339
        }
340
341 56
        return strtolower($host);
342
    }
343
344
    /**
345
     * @param int|null $port
346
     *
347
     * @throws \InvalidArgumentException If the port is invalid
348
     *
349
     * @return int|null
350
     */
351 38
    private function filterPort($port)
352
    {
353 38
        if (null === $port) {
354 28
            return;
355
        }
356
357 13
        $port = (int) $port;
358 13
        if (1 > $port || 0xffff < $port) {
359 2
            throw new \InvalidArgumentException(sprintf('Invalid port: %d. Must be between 1 and 65535', $port));
360
        }
361
362 11
        return self::isNonStandardPort($this->scheme, $port) ? $port : null;
363
    }
364
365
    /**
366
     * Filters the path of a URI.
367
     *
368
     * @param string $path
369
     *
370
     * @throws \InvalidArgumentException If the path is invalid
371
     *
372
     * @return string
373
     */
374 80 View Code Duplication
    private function filterPath($path): string
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
375
    {
376 80
        if (!is_string($path)) {
377 1
            throw new \InvalidArgumentException('Path must be a string');
378
        }
379
380 79
        return preg_replace_callback(
381 79
            '/(?:[^'.self::$charUnreserved.self::$charSubDelims.'%:@\/]++|%(?![A-Fa-f0-9]{2}))/',
382 79
            [$this, 'rawurlencodeMatchZero'],
383 79
            $path
384
        );
385
    }
386
387
    /**
388
     * Filters the query string or fragment of a URI.
389
     *
390
     * @param string $str
391
     *
392
     * @throws \InvalidArgumentException If the query or fragment is invalid
393
     *
394
     * @return string
395
     */
396 39 View Code Duplication
    private function filterQueryAndFragment($str): string
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
397
    {
398 39
        if (!is_string($str)) {
399 2
            throw new \InvalidArgumentException('Query and fragment must be a string');
400
        }
401
402 37
        return preg_replace_callback(
403 37
            '/(?:[^'.self::$charUnreserved.self::$charSubDelims.'%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/',
404 37
            [$this, 'rawurlencodeMatchZero'],
405 37
            $str
406
        );
407
    }
408
409 6
    private function rawurlencodeMatchZero(array $match): string
410
    {
411 6
        return rawurlencode($match[0]);
412
    }
413
}
414