Uri::getHost()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Linna Http Message.
5
 *
6
 * @author Sebastian Rapetti <[email protected]>
7
 * @copyright (c) 2019, Sebastian Rapetti
8
 * @license http://opensource.org/licenses/MIT MIT License
9
 */
10
declare(strict_types=1);
11
12
namespace Linna\Http\Message;
13
14
use InvalidArgumentException;
15
use Psr\Http\Message\UriInterface;
16
17
/**
18
 * PSR-7 Uri Implementation.
19
 */
20
class Uri implements UriInterface
21
{
22
    use UriTrait;
23
24
    /**
25
     * @var array Standard schemes.
26
     */
27
    protected $standardSchemes = [
28
        'http'  => 80,
29
        'https' => 443,
30
    ];
31
32
    /**
33
     * @var string Url scheme.
34
     */
35
    protected $scheme = '';
36
37
    /**
38
     * @var string Url host.
39
     */
40
    protected $host = '';
41
42
    /**
43
     * @var int Url port.
44
     */
45
    protected $port =  0;
46
47
    /**
48
     * @var string Url authority user
49
     */
50
    protected $user = '';
51
52
    /**
53
     * @var string Url authority password
54
     */
55
    protected $pass = '';
56
57
    /**
58
     * @var string Url path
59
     */
60
    protected $path = '';
61
62
    /**
63
     * @var string Url query
64
     */
65
    protected $query = '';
66
67
    /**
68
     * @var string Url fragment
69
     */
70
    protected $fragment = '';
71
72
    /**
73
     * Constructor.
74
     *
75
     * @param string $uri
76
     *
77
     * @throws InvalidArgumentException
78
     */
79 104
    public function __construct(string $uri)
80
    {
81 104
        if (($parsedUrl = \parse_url($uri)) === false) {
82 1
            throw new InvalidArgumentException('Bad URI provided.');
83
        }
84
85
        [
86 103
            'scheme'   => $this->scheme,
87 103
            'host'     => $this->host,
88 103
            'port'     => $this->port,
89 103
            'user'     => $this->user,
90 103
            'pass'     => $this->pass,
91 103
            'path'     => $this->path,
92 103
            'query'    => $this->query,
93 103
            'fragment' => $this->fragment,
94 103
        ] = \array_replace_recursive([
95 103
            'scheme'   => '',
96
            'host'     => '',
97
            'port'     => 0,
98
            'user'     => '',
99
            'pass'     => '',
100
            'path'     => '',
101
            'query'    => '',
102
            'fragment' => '',
103 103
        ], $parsedUrl);
104 103
    }
105
106
    /**
107
     * Retrieve the scheme component of the URI.
108
     *
109
     * If no scheme is present, this method MUST return an empty string.
110
     *
111
     * The value returned MUST be normalized to lowercase, per RFC 3986
112
     * Section 3.1.
113
     *
114
     * The trailing ":" character is not part of the scheme and MUST NOT be
115
     * added.
116
     *
117
     * @see https://tools.ietf.org/html/rfc3986#section-3.1
118
     *
119
     * @return string The URI scheme.
120
     */
121 2
    public function getScheme(): string
122
    {
123 2
        return \strtolower($this->scheme);
124
    }
125
126
    /**
127
     * Retrieve the authority component of the URI.
128
     *
129
     * If no authority information is present, this method MUST return an empty
130
     * string.
131
     *
132
     * The authority syntax of the URI is:
133
     *
134
     * <pre>
135
     * [user-info@]host[:port]
136
     * </pre>
137
     *
138
     * If the port component is not set or is the standard port for the current
139
     * scheme, it SHOULD NOT be included.
140
     *
141
     * @see https://tools.ietf.org/html/rfc3986#section-3.2
142
     *
143
     * @return string The URI authority, in "[user-info@]host[:port]" format.
144
     */
145 33
    public function getAuthority(): string
146
    {
147 33
        if ($this->host === '') {
148 7
            return '';
149
        }
150
151 26
        $authority = $this->host;
152
153 26
        if ($this->user !== '') {
154 18
            $authority = $this->getUserInfo().'@'.$authority;
155
        }
156
157 26
        if ($this->port !== 0) {
158 17
            $authority .= ':'.$this->port;
159
        }
160
161 26
        return $authority;
162
    }
163
164
    /**
165
     * Retrieve the user information component of the URI.
166
     *
167
     * If no user information is present, this method MUST return an empty
168
     * string.
169
     *
170
     * If a user is present in the URI, this will return that value;
171
     * additionally, if the password is also present, it will be appended to the
172
     * user value, with a colon (":") separating the values.
173
     *
174
     * The trailing "@" character is not part of the user information and MUST
175
     * NOT be added.
176
     *
177
     * @return string The URI user information, in "username[:password]" format.
178
     */
179 21
    public function getUserInfo(): string
180
    {
181 21
        $user = $this->user;
182
183 21
        if ($this->pass !== '' && $this->pass !== null) {
184 17
            $user .= ':'.$this->pass;
185
        }
186
187 21
        return ($user !== '') ? $user : '';
188
    }
189
190
    /**
191
     * Retrieve the host component of the URI.
192
     *
193
     * If no host is present, this method MUST return an empty string.
194
     *
195
     * The value returned MUST be normalized to lowercase, per RFC 3986
196
     * Section 3.2.2.
197
     *
198
     * @see http://tools.ietf.org/html/rfc3986#section-3.2.2
199
     *
200
     * @return string The URI host.
201
     */
202 8
    public function getHost(): string
203
    {
204 8
        return \strtolower($this->host);
205
    }
206
207
    /**
208
     * Retrieve the port component of the URI.
209
     *
210
     * If a port is present, and it is non-standard for the current scheme,
211
     * this method MUST return it as an integer. If the port is the standard port
212
     * used with the current scheme, this method SHOULD return null.
213
     *
214
     * If no port is present, and no scheme is present, this method MUST return
215
     * a zero value.
216
     *
217
     * If no port is present, but a scheme is present, this method MAY return
218
     * the standard port for that scheme, but SHOULD return zero.
219
     *
220
     * @return int The URI port.
221
     */
222 12
    public function getPort(): int
223
    {
224 12
        $scheme = $this->scheme;
225 12
        $port = $this->port;
226
227 12
        $standardPort = $this->checkStandardPortForCurretScheme($scheme, $port, $this->standardSchemes);
228 12
        $standardScheme = \array_key_exists($scheme, $this->standardSchemes);
229
230
        //scheme present and port standard - return 0
231
        //scheme present and port non standard - return port
232 12
        if ($standardPort && $standardScheme) {
233 1
            return $this->getPortForStandardScheme($standardPort, $port);
234
        }
235
236
        //scheme present and standard, port non present - return port
237
        //scheme non standard, port present - return port
238 11
        return $this->getNonStandardPort($port, $scheme, $standardScheme, $this->standardSchemes);
239
    }
240
241
    /**
242
     * Retrieve the path component of the URI.
243
     *
244
     * The path can either be empty or absolute (starting with a slash) or
245
     * rootless (not starting with a slash). Implementations MUST support all
246
     * three syntaxes.
247
     *
248
     * Normally, the empty path "" and absolute path "/" are considered equal as
249
     * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically
250
     * do this normalization because in contexts with a trimmed base path, e.g.
251
     * the front controller, this difference becomes significant. It's the task
252
     * of the user to handle both "" and "/".
253
     *
254
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
255
     * any characters. To determine what characters to encode, please refer to
256
     * RFC 3986, Sections 2 and 3.3.
257
     *
258
     * As an example, if the value should include a slash ("/") not intended as
259
     * delimiter between path segments, that value MUST be passed in encoded
260
     * form (e.g., "%2F") to the instance.
261
     *
262
     * @see https://tools.ietf.org/html/rfc3986#section-2
263
     * @see https://tools.ietf.org/html/rfc3986#section-3.3
264
     *
265
     * @return string The URI path.
266
     */
267 33
    public function getPath(): string
268
    {
269 33
        return $this->path;
270
    }
271
272
    /**
273
     * Retrieve the query string of the URI.
274
     *
275
     * If no query string is present, this method MUST return an empty string.
276
     *
277
     * The leading "?" character is not part of the query and MUST NOT be
278
     * added.
279
     *
280
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
281
     * any characters. To determine what characters to encode, please refer to
282
     * RFC 3986, Sections 2 and 3.4.
283
     *
284
     * As an example, if a value in a key/value pair of the query string should
285
     * include an ampersand ("&") not intended as a delimiter between values,
286
     * that value MUST be passed in encoded form (e.g., "%26") to the instance.
287
     *
288
     * @see https://tools.ietf.org/html/rfc3986#section-2
289
     * @see https://tools.ietf.org/html/rfc3986#section-3.4
290
     *
291
     * @return string The URI query string.
292
     */
293 10
    public function getQuery(): string
294
    {
295 10
        return $this->query;
296
    }
297
298
    /**
299
     * Retrieve the fragment component of the URI.
300
     *
301
     * If no fragment is present, this method MUST return an empty string.
302
     *
303
     * The leading "#" character is not part of the fragment and MUST NOT be
304
     * added.
305
     *
306
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
307
     * any characters. To determine what characters to encode, please refer to
308
     * RFC 3986, Sections 2 and 3.5.
309
     *
310
     * @see https://tools.ietf.org/html/rfc3986#section-2
311
     * @see https://tools.ietf.org/html/rfc3986#section-3.5
312
     *
313
     * @return string The URI fragment.
314
     */
315 4
    public function getFragment(): string
316
    {
317 4
        return $this->fragment;
318
    }
319
320
    /**
321
     * Return an instance with the specified scheme.
322
     *
323
     * This method MUST retain the state of the current instance, and return
324
     * an instance that contains the specified scheme.
325
     *
326
     * Implementations MUST support the schemes "http" and "https" case
327
     * insensitively, and MAY accommodate other schemes if required.
328
     *
329
     * An empty scheme is equivalent to removing the scheme.
330
     *
331
     * @param string $scheme The scheme to use with the new instance.
332
     *
333
     * @return static A new instance with the specified scheme.
334
     *
335
     * @throws InvalidArgumentException for invalid or unsupported schemes.
336
     */
337 2
    public function withScheme(string $scheme): UriInterface
338
    {
339 2
        if (\array_key_exists($scheme, $this->standardSchemes)) {
340 1
            $new = clone $this;
341 1
            $new->scheme = $scheme;
342
343 1
            return $new;
344
        }
345
346 1
        throw new InvalidArgumentException('Invalid or unsupported scheme provided.');
347
    }
348
349
    /**
350
     * Return an instance with the specified user information.
351
     *
352
     * This method MUST retain the state of the current instance, and return
353
     * an instance that contains the specified user information.
354
     *
355
     * Password is optional, but the user information MUST include the
356
     * user; an empty string for the user is equivalent to removing user
357
     * information.
358
     *
359
     * @param string $user The user name to use for authority.
360
     * @param string $password The password associated with $user.
361
     *
362
     * @return static A new instance with the specified user information.
363
     */
364 3
    public function withUserInfo(string $user, string $password = ''): UriInterface
365
    {
366 3
        $new = clone $this;
367 3
        $new->user = $user;
368 3
        $new->pass = $password;
369
370 3
        return $new;
371
    }
372
373
    /**
374
     * Return an instance with the specified host.
375
     *
376
     * This method MUST retain the state of the current instance, and return
377
     * an instance that contains the specified host.
378
     *
379
     * An empty host value is equivalent to removing the host.
380
     *
381
     * @param string $host The hostname to use with the new instance.
382
     *
383
     * @return static A new instance with the specified host.
384
     *
385
     * @throws InvalidArgumentException for invalid hostnames.
386
     */
387 2
    public function withHost(string $host): UriInterface
388
    {
389 2
        if (\filter_var($host, \FILTER_VALIDATE_DOMAIN, \FILTER_FLAG_HOSTNAME) === false) {
390 1
            throw new InvalidArgumentException('Invalid host provided.');
391
        }
392
393 1
        $new = clone $this;
394 1
        $new->host = $host;
395
396 1
        return $new;
397
    }
398
399
    /**
400
     * Return an instance with the specified port.
401
     *
402
     * This method MUST retain the state of the current instance, and return
403
     * an instance that contains the specified port.
404
     *
405
     * Implementations MUST raise an exception for ports outside the
406
     * established TCP and UDP port ranges.
407
     *
408
     * A zero value provided for the port is equivalent to removing the port
409
     * information.
410
     *
411
     * @param int $port The port to use with the new instance; a zero value
412
     *                  removes the port information.
413
     *
414
     * @return static A new instance with the specified port.
415
     *
416
     * @throws InvalidArgumentException for invalid ports.
417
     */
418 4
    public function withPort(int $port = 0): UriInterface
419
    {
420 4
        if ($port > -1 && $port < 65536) {
421 2
            $new = clone $this;
422 2
            $new->port = $port;
423
424 2
            return $new;
425
        }
426
427 2
        throw new InvalidArgumentException("Invalid port {$port} number provided.");
428
    }
429
430
    /**
431
     * Return an instance with the specified path.
432
     *
433
     * This method MUST retain the state of the current instance, and return
434
     * an instance that contains the specified path.
435
     *
436
     * The path can either be empty or absolute (starting with a slash) or
437
     * rootless (not starting with a slash). Implementations MUST support all
438
     * three syntaxes.
439
     *
440
     * If the path is intended to be domain-relative rather than path relative then
441
     * it must begin with a slash ("/"). Paths not starting with a slash ("/")
442
     * are assumed to be relative to some base path known to the application or
443
     * consumer.
444
     *
445
     * Users can provide both encoded and decoded path characters.
446
     * Implementations ensure the correct encoding as outlined in getPath().
447
     *
448
     * @param string $path The path to use with the new instance.
449
     *
450
     * @return static A new instance with the specified path.
451
     *
452
     * @throws InvalidArgumentException for invalid paths.
453
     */
454 3
    public function withPath(string $path): UriInterface
455
    {
456 3
        if (\strpos($path, '?') !== false) {
457 1
            throw new InvalidArgumentException('Invalid path provided; must not contain a query string.');
458
        }
459
460 2
        if (\strpos($path, '#') !== false) {
461 1
            throw new InvalidArgumentException('Invalid path provided; must not contain a URI fragment.');
462
        }
463
464 1
        $new = clone $this;
465 1
        $new->path = $path;
466
467 1
        return $new;
468
    }
469
470
    /**
471
     * Return an instance with the specified query string.
472
     *
473
     * This method MUST retain the state of the current instance, and return
474
     * an instance that contains the specified query string.
475
     *
476
     * Users can provide both encoded and decoded query characters.
477
     * Implementations ensure the correct encoding as outlined in getQuery().
478
     *
479
     * An empty query string value is equivalent to removing the query string.
480
     *
481
     * @param string $query The query string to use with the new instance.
482
     *
483
     * @return static A new instance with the specified query string.
484
     *
485
     * @throws InvalidArgumentException for invalid query strings.
486
     */
487 4
    public function withQuery(string $query): UriInterface
488
    {
489 4
        if (\strpos($query, '#') === false) {
490 3
            $new = clone $this;
491 3
            $new->query = (\strpos($query, '?') !== false) ? \substr($query, 1) : $query;
492
493 3
            return $new;
494
        }
495
496
497
498 1
        throw new InvalidArgumentException('Query string must not include a URI fragment.');
499
    }
500
501
    /**
502
     * Return an instance with the specified URI fragment.
503
     *
504
     * This method MUST retain the state of the current instance, and return
505
     * an instance that contains the specified URI fragment.
506
     *
507
     * Users can provide both encoded and decoded fragment characters.
508
     * Implementations ensure the correct encoding as outlined in getFragment().
509
     *
510
     * An empty fragment value is equivalent to removing the fragment.
511
     *
512
     * @param string $fragment The fragment to use with the new instance.
513
     *
514
     * @return static A new instance with the specified fragment.
515
     */
516 3
    public function withFragment(string $fragment): UriInterface
517
    {
518 3
        $new = clone $this;
519 3
        $new->fragment = (\strpos($fragment, '#') !== false) ? \substr($fragment, 1) : $fragment;
520
521 3
        return $new;
522
    }
523
524
    /**
525
     * Return the string representation as a URI reference.
526
     *
527
     * Depending on which components of the URI are present, the resulting
528
     * string is either a full URI or relative reference according to RFC 3986,
529
     * Section 4.1. The method concatenates the various components of the URI,
530
     * using the appropriate delimiters:
531
     *
532
     * - If a scheme is present, it MUST be suffixed by ":".
533
     * - If an authority is present, it MUST be prefixed by "//".
534
     * - The path can be concatenated without delimiters. But there are two
535
     *   cases where the path has to be adjusted to make the URI reference
536
     *   valid as PHP does not allow to throw an exception in __toString():
537
     *     - If the path is rootless and an authority is present, the path MUST
538
     *       be prefixed by "/".
539
     *     - If the path is starting with more than one "/" and no authority is
540
     *       present, the starting slashes MUST be reduced to one.
541
     * - If a query is present, it MUST be prefixed by "?".
542
     * - If a fragment is present, it MUST be prefixed by "#".
543
     *
544
     * @see http://tools.ietf.org/html/rfc3986#section-4.1
545
     *
546
     * @return string
547
     */
548 26
    public function __toString(): string
549
    {
550 26
        $scheme = $this->scheme;
551 26
        $query = $this->query;
552 26
        $fragment = $this->fragment;
553
554 26
        return $this->createUriString(
555 26
            ($scheme !== '') ? $scheme.'://' : '',
556 26
            $this->getAuthority(),
557 26
            $this->getPath(),
558 26
            ($query !== '') ? '?'.$query : '',
559 26
            ($fragment !== '') ? '#'.$fragment : ''
560
        );
561
    }
562
}
563