Uri::getHost()   A
last analyzed

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
namespace Kambo\Http\Message;
3
4
// \Spl
5
use InvalidArgumentException;
6
7
// \Psr
8
use Psr\Http\Message\UriInterface;
9
10
// \Http\Message
11
use Kambo\Http\Message\Utils\UriValidator;
12
13
/**
14
 * Value object representing a URI.
15
 *
16
 * This class represent URIs according to RFC 3986 and to
17
 * provide methods for most common operations. Additional functionality for
18
 * working with URIs can be provided on top of the object or externally.
19
 * Its primary use is for HTTP requests, but may also be used in other
20
 * contexts.
21
 *
22
 * Instances of the uri are considered immutable; all methods that
23
 * change state retain the internal state of the current message and return
24
 * an instance that contains the changed state.
25
 *
26
 * @link http://tools.ietf.org/html/rfc3986 (the URI specification)
27
 *
28
 * @package Kambo\Http\Message
29
 * @author  Bohuslav Simek <[email protected]>
30
 * @license MIT
31
 */
32
class Uri implements UriInterface
33
{
34
    const HTTP  = 'http';
35
    const HTTPS = 'https';
36
37
    /**
38
     * Http port
39
     *
40
     * @var integer
41
     */
42
    const HTTP_PORT = 80;
43
44
    /**
45
     * Https port
46
     *
47
     * @var integer
48
     */
49
    const HTTPS_PORT = 443;
50
51
    /**
52
     * Valid URI schemas
53
     * Schema can be http, https or empty.
54
     *
55
     * @var array in format [ string "schema"=> boolean true (true valid), ... ]
56
     */
57
    private $validSchema = [
58
        '' => true,
59
        self::HTTPS => true,
60
        self::HTTP => true,
61
    ];
62
63
    /**
64
     * URL scheme of the URI.
65
     * Default value is an empty string.
66
     *
67
     * @var string
68
     */
69
    private $scheme = '';
70
71
    /**
72
     * Path component of the URI.
73
     * Default value is an empty string.
74
     *
75
     * @var string
76
     */
77
    private $path = '';
78
79
    /**
80
     * Query string of the URI.
81
     * Default value is an empty string.
82
     *
83
     * @var string
84
     */
85
    private $query = '';
86
87
    /**
88
     * Fragment component of the URI.
89
     * Default value is an empty string.
90
     *
91
     * @var string
92
     */
93
    private $fragment = '';
94
95
    /**
96
     * User part of the URI.
97
     * Default value is an empty string.
98
     *
99
     * @var string
100
     */
101
    private $user = '';
102
103
    /**
104
     * Password part of the URI.
105
     * Default value is an empty string.
106
     *
107
     * @var string
108
     */
109
    private $password = '';
110
111
    /**
112
     * Host component of the URI eg. foo.bar
113
     *
114
     * @var string
115
     */
116
    private $host;
117
118
    /**
119
     * Port component of the URI eg. 443.
120
     *
121
     * @var integer
122
     */
123
    private $port;
124
125
    /**
126
     * Instance of uri validator
127
     *
128
     * @var UriValidator
129
     */
130
    private $validator;
131
132
    /**
133
     * Create new Uri.
134
     *
135
     * @param string $scheme   Uri scheme.
136
     * @param string $host     Uri host.
137
     * @param int    $port     Uri port number.
138
     * @param string $path     Uri path.
139
     * @param string $query    Uri query string.
140
     * @param string $fragment Uri fragment.
141
     * @param string $user     Uri user.
142
     * @param string $password Uri password.
143
     */
144 73
    public function __construct(
145
        $scheme,
146
        $host,
147
        $port = null,
148
        $path = '/',
149
        $query = '',
150
        $fragment = '',
151
        $user = '',
152
        $password = ''
153
    ) {
154 73
        $this->scheme   = $this->normalizeScheme($scheme);
155 73
        $this->host     = strtolower($host);
156 73
        $this->port     = $port;
157 73
        $this->path     = $this->normalizePath($path);
158 72
        $this->query    = $this->urlEncode($query);
159 72
        $this->fragment = $fragment;
160 72
        $this->user     = $user;
161 72
        $this->password = $password;
162
163 72
        $this->validator = new UriValidator();
164 72
    }
165
166
    /**
167
     * Retrieve the scheme component of the URI.
168
     *
169
     * If no scheme is present, this method return an empty string.
170
     *
171
     * Underline implementation normalize schema to lowercase, as described in RFC 3986
172
     * Section 3.1.
173
     *
174
     * The trailing ":" character is not part of the scheme and is not added.
175
     *
176
     * @see https://tools.ietf.org/html/rfc3986#section-3.1
177
     *
178
     * @return string The URI scheme.
179
     */
180 11
    public function getScheme()
181
    {
182 11
        return $this->scheme;
183
    }
184
185
    /**
186
     * Retrieve the authority component of the URI.
187
     *
188
     * If no authority information is present, this method returns an empty
189
     * string.
190
     *
191
     * The authority syntax of the URI is:
192
     *
193
     * <pre>
194
     * [user-info@]host[:port]
195
     * </pre>
196
     *
197
     * If the port component is not set or is the standard port for the current
198
     * scheme, it is not included.
199
     *
200
     * @see https://tools.ietf.org/html/rfc3986#section-3.2
201
     *
202
     * @return string The URI authority, in "[user-info@]host[:port]" format.
203
     */
204 7
    public function getAuthority()
205
    {
206 7
        $port     = $this->getPort();
207 7
        $userInfo = $this->getUserInfo();
208
209 7
        return ($userInfo ? $userInfo . '@' : '') . $this->getHost() . ($port ? ':' . $port : '');
210
    }
211
212
    /**
213
     * Retrieve the user information component of the URI.
214
     *
215
     * If no user information is present, this method returns an empty string.
216
     *
217
     * If a user is present in the URI, this method return that value;
218
     * additionally, if the password is also present, it will be appended to the
219
     * user value, with a colon (":") separating the values.
220
     *
221
     * The trailing "@" character is not part of the user information and is not added.
222
     *
223
     * @return string The URI user information, in "username[:password]" format.
224
     */
225 13
    public function getUserInfo()
226
    {
227 13
        $userInfo = '';
228 13
        if (!empty($this->user)) {
229 7
            $userInfo = $this->user;
230 7
            if (!empty($this->password)) {
231 6
                $userInfo .= ':' . $this->password;
232 6
            }
233 7
        }
234
235 13
        return $userInfo;
236
    }
237
238
    /**
239
     * Retrieve the host component of the URI.
240
     *
241
     * If no host is present, this method returns an empty string.
242
     *
243
     * Underline implementation normalize the host name to lowercase, as described in RFC 3986
244
     * Section 3.2.2.
245
     *
246
     * @see http://tools.ietf.org/html/rfc3986#section-3.2.2
247
     *
248
     * @return string The URI host.
249
     */
250 21
    public function getHost()
251
    {
252 21
        return $this->host;
253
    }
254
255
    /**
256
     * Retrieve the port component of the URI.
257
     *
258
     * If a port is present, and it is non-standard for the current scheme,
259
     * this method return it as an integer. If the port is the standard port
260
     * used with the current scheme, this method return null.
261
     *
262
     * If no port is present, and no scheme is present, this method returns
263
     * a null value.
264
     *
265
     * If no port is present, but a scheme is present, this method MAY return
266
     * the standard port for that scheme, but SHOULD return null.
267
     *
268
     * @return null|int The URI port.
269
     */
270 13
    public function getPort()
271
    {
272 13
        return $this->isStandardPort() ? null : $this->port;
273
    }
274
275
    /**
276
     * Retrieve the path component of the URI.
277
     *
278
     * The path can either be empty or absolute (starting with a slash) or
279
     * rootless (not starting with a slash). Implementation support all
280
     * three syntaxes.
281
     *
282
     * Normally, the empty path "" and absolute path "/" are considered equal as
283
     * defined in RFC 7230 Section 2.7.3. But this method does not automatically
284
     * do this normalization because in contexts with a trimmed base path, e.g.
285
     * the front controller, this difference becomes significant. It's the task
286
     * of the user to handle both "" and "/".
287
     *
288
     * The value returned is percent-encoded, and underline implementation ensure that 
289
     * the value is not double-encode. Encoded characters are defined in 
290
     * RFC 3986, Sections 2 and 3.4.
291
     *
292
     * As an example, if the value should include a slash ("/") not intended as
293
     * delimiter between path segments, that value is passed in encoded
294
     * form (e.g., "%2F") to the instance.
295
     *
296
     * @see https://tools.ietf.org/html/rfc3986#section-2
297
     * @see https://tools.ietf.org/html/rfc3986#section-3.3
298
     *
299
     * @return string The URI path.
300
     */
301 19
    public function getPath()
302
    {
303 19
        return $this->path;
304
    }
305
306
    /**
307
     * Retrieve the query string of the URI.
308
     *
309
     * If no query string is present, this method returns an empty string.
310
     *
311
     * The leading "?" character is not part of the query and it is not added.
312
     *
313
     * The value returned is percent-encoded, and implementation ensure that 
314
     * the value is not double-encode. Encoded characters are defined in 
315
     * RFC 3986, Sections 2 and 3.4.
316
     *
317
     * As an example, if a value in a key/value pair of the query string should
318
     * include an ampersand ("&") not intended as a delimiter between values,
319
     * that value is passed in encoded form (e.g., "%26") to the instance.
320
     *
321
     * @see https://tools.ietf.org/html/rfc3986#section-2
322
     * @see https://tools.ietf.org/html/rfc3986#section-3.4
323
     *
324
     * @return string The URI query string.
325
     */
326 20
    public function getQuery()
327
    {
328 20
        return $this->query;
329
    }
330
331
332
    /**
333
     * Retrieve the fragment component of the URI.
334
     *
335
     * If no fragment is present, this method returns an empty string.
336
     *
337
     * The leading "#" character is not part of the fragment and it is not
338
     * added.
339
     *
340
     * The value returned is percent-encoded, and underline implementation ensure that 
341
     * the value is not double-encode. Encoded characters are defined in 
342
     * RFC 3986, Sections 2 and 3.4.
343
     *
344
     * @see https://tools.ietf.org/html/rfc3986#section-2
345
     * @see https://tools.ietf.org/html/rfc3986#section-3.5
346
     *
347
     * @return string The URI fragment.
348
     */
349 11
    public function getFragment()
350
    {
351 11
        return $this->fragment;
352
    }
353
354
    /**
355
     * Return an instance with the specified scheme.
356
     *
357
     * This method retains the state of the current instance, and returns
358
     * an instance that contains the specified scheme.
359
     *
360
     * Implementations support the schemes "http" and "https" case
361
     * insensitively.
362
     *
363
     * An empty scheme is equivalent to removing the scheme.
364
     *
365
     * @param string $scheme The scheme to use with the new instance.
366
     *
367
     * @return self A new instance with the specified scheme.
368
     *
369
     * @throws \InvalidArgumentException for invalid or unsupported schemes.
370
     */
371 3
    public function withScheme($scheme)
372
    {
373 3
        $clone         = clone $this;
374 3
        $clone->scheme = $this->normalizeScheme($scheme);
375
376 1
        return $clone;
377
    }
378
379
    /**
380
     * Return an instance with the specified user information.
381
     *
382
     * This method retains the state of the current instance, and returns
383
     * an instance that contains the specified user information.
384
     *
385
     * Password is optional, but the user information include the
386
     * user; an empty string for the user is equivalent to removing user
387
     * information.
388
     *
389
     * @param string      $user     The user name to use for authority.
390
     * @param null|string $password The password associated with $user.
391
     *
392
     * @return self A new instance with the specified user information.
393
     */
394 2
    public function withUserInfo($user, $password = null)
395
    {
396 2
        $clone           = clone $this;
397 2
        $clone->user     = $user;
398 2
        $clone->password = $password;
399
400 2
        return $clone;
401
    }
402
403
    /**
404
     * Return an instance with the specified host.
405
     *
406
     * This method retains the state of the current instance, and returns
407
     * an instance that contains the specified host.
408
     *
409
     * An empty host value is equivalent to removing the host.
410
     *
411
     * @param string $host The hostname to use with the new instance.
412
     *
413
     * @return self A new instance with the specified host.
414
     *
415
     * @throws \InvalidArgumentException for invalid hostnames.
416
     */
417 1
    public function withHost($host)
418
    {
419 1
        $clone       = clone $this;
420 1
        $clone->host = strtolower($host);
421
422 1
        return $clone;
423
    }
424
425
    /**
426
     * Return an instance with the specified port.
427
     *
428
     * This method retains the state of the current instance, and return
429
     * an instance that contains the specified port.
430
     *
431
     * Method raise an exception for ports outside the
432
     * established TCP and UDP port ranges.
433
     *
434
     * A null value provided for the port is equivalent to removing the port
435
     * information.
436
     *
437
     * @param null|int $port The port to use with the new instance; a null value
438
     *                       removes the port information.
439
     *
440
     * @return self A new instance with the specified port.
441
     *
442
     * @throws \InvalidArgumentException for invalid ports.
443
     */
444 2
    public function withPort($port)
445
    {
446 2
        $this->validator->validatePort($port);
447
448 1
        $clone       = clone $this;
449 1
        $clone->port = $port;
450
451 1
        return $clone;
452
    }
453
454
    /**
455
     * Return an instance with the specified path.
456
     *
457
     * This method retains the state of the current instance, and return
458
     * an instance that contains the specified path.
459
     *
460
     * The path can either be empty or absolute (starting with a slash) or
461
     * rootless (not starting with a slash). Implementations support all
462
     * three syntaxes.
463
     *
464
     * If the path is intended to be domain-relative rather than path relative then
465
     * it must begin with a slash ("/"). Paths not starting with a slash ("/")
466
     * are assumed to be relative to some base path known to the application or
467
     * consumer.
468
     *
469
     * Users can provide both encoded and decoded path characters.
470
     * Implementations ensure the correct encoding as outlined in getPath().
471
     *
472
     * @param string $path The path to use with the new instance.
473
     *
474
     * @return self A new instance with the specified path.
475
     *
476
     * @throws \InvalidArgumentException for invalid paths.
477
     */
478 4
    public function withPath($path)
479
    {
480 4
        $this->validator->validatePath($path);
481
482 1
        $clone       = clone $this;
483 1
        $clone->path = $this->urlEncode((string) $path);
484
485 1
        return $clone;
486
    }
487
488
    /**
489
     * Return an instance with the specified query string.
490
     *
491
     * This method retains the state of the current instance, and return
492
     * an instance that contains the specified query string.
493
     *
494
     * Users can provide both encoded and decoded query characters.
495
     * Implementation ensure the correct encoding as outlined in getQuery().
496
     *
497
     * An empty query string value is equivalent to removing the query string.
498
     *
499
     * @param string $query The query string to use with the new instance.
500
     *
501
     * @return self A new instance with the specified query string.
502
     *
503
     * @throws \InvalidArgumentException for invalid query strings.
504
     */
505 5
    public function withQuery($query)
506
    {
507 5
        $this->validator->validateQuery($query);
508
509 2
        $clone        = clone $this;
510 2
        $clone->query = $this->urlEncode((string) $query);
511
512 2
        return $clone;
513
    }
514
515
    /**
516
     * Return an instance with the specified URI fragment.
517
     *
518
     * This method retains the state of the current instance, and return
519
     * an instance that contains the specified URI fragment.
520
     *
521
     * Users can provide both encoded and decoded fragment characters.
522
     * Implementation ensure the correct encoding as outlined in getFragment().
523
     *
524
     * An empty fragment value is equivalent to removing the fragment.
525
     *
526
     * @param string $fragment The fragment to use with the new instance.
527
     *
528
     * @return self A new instance with the specified fragment.
529
     */
530 1
    public function withFragment($fragment)
531
    {
532 1
        $clone           = clone $this;
533 1
        $clone->fragment = $fragment;
534
535 1
        return $clone;
536
    }
537
538
    /**
539
     * Return the string representation as a URI reference.
540
     *
541
     * Depending on which components of the URI are present, the resulting
542
     * string is either a full URI or relative reference according to RFC 3986,
543
     * Section 4.1. The method concatenates the various components of the URI,
544
     * using the appropriate delimiters:
545
     *
546
     * - If a scheme is present, it is suffixed by ":".
547
     * - If an authority is present, it is prefixed by "//".
548
     * - The path can be concatenated without delimiters. But there are two
549
     *   cases where the path has to be adjusted to make the URI reference
550
     *   valid as PHP does not allow to throw an exception in __toString():
551
     *     - If the path is rootless and an authority is present, the path is
552
     *       be prefixed by "/".
553
     *     - If the path is starting with more than one "/" and no authority is
554
     *       present, the starting slashes are be reduced to one.
555
     * - If a query is present, it is prefixed by "?".
556
     * - If a fragment is present, it is prefixed by "#".
557
     *
558
     *         foo://example.com:8042/over/there?name=ferret#nose
559
     *         \_/   \______________/\_________/ \_________/ \__/
560
     *          |           |            |            |        |
561
     *       scheme     authority       path        query   fragment   
562
     *  
563
     * @see http://tools.ietf.org/html/rfc3986#section-4.1
564
     *
565
     * @return string
566
     */
567 7
    public function __toString()
568
    {
569 7
        $scheme    = $this->getScheme();
570 7
        $authority = $this->getAuthority();
571 7
        $path      = $this->getPath();
572 7
        $query     = $this->getQuery();
573 7
        $fragment  = $this->getFragment();
574
575 7
        $path = '/' . ltrim($path, '/');
576
577 7
        return ($scheme ? $scheme . ':' : '') . ($authority ? '//' . $authority : '')
578 7
            . $path . ($query ? '?' . $query : '') . ($fragment ? '#' . $fragment : '');
579
    }
580
581
    // ------------ PRIVATE METHODS
582
583
    /**
584
     * Check if Uri use a standard port.
585
     *
586
     * @return bool
587
     */
588 13
    private function isStandardPort()
589
    {
590 13
        return ($this->scheme === self::HTTP && $this->port === self::HTTP_PORT)
591 13
            || ($this->scheme === self::HTTPS && $this->port === self::HTTPS_PORT);
592
    }
593
594
    /**
595
     * Normalize scheme part of Uri.
596
     *
597
     * @param string $scheme Raw Uri scheme.
598
     *
599
     * @return string Normalized Uri
600
     *
601
     * @throws InvalidArgumentException If the Uri scheme is not a string.
602
     * @throws InvalidArgumentException If Uri scheme is not "", "https", or "http".
603
     */
604 73
    private function normalizeScheme($scheme)
605
    {
606 73
        if (!is_string($scheme) && !method_exists($scheme, '__toString')) {
607 1
            throw new InvalidArgumentException('Uri scheme must be a string');
608
        }
609
610 73
        $scheme = str_replace('://', '', strtolower((string) $scheme));
611 73
        if (!isset($this->validSchema[$scheme])) {
612 1
            throw new InvalidArgumentException('Uri scheme must be one of: "", "https", "http"');
613
        }
614
615 73
        return $scheme;
616
    }
617
618
    /**
619
     * Normalize path part of Uri and ensure it is properly encoded..
620
     *
621
     * @param string $path Raw Uri path.
622
     *
623
     * @return string Normalized Uri path
624
     *
625
     * @throws InvalidArgumentException If the Uri scheme is not a string.
626
     */
627 73
    private function normalizePath($path)
628
    {
629 73
        if (!is_string($path) && !method_exists($path, '__toString')) {
630 1
            throw new InvalidArgumentException('Uri path must be a string');
631
        }
632
633 72
        $path = $this->urlEncode($path);
634
635 72
        if (empty($path)) {
636 1
            return '';
637
        }
638
639 71
        if ($path[0] !== '/') {
640 1
            return $path;
641
        }
642
643
        // Ensure only one leading slash
644 70
        return '/' . ltrim($path, '/');
645
    }
646
647
    /**
648
     * Url encode 
649
     *
650
     * This method percent-encodes all reserved
651
     * characters in the provided path string. This method
652
     * will NOT double-encode characters that are already
653
     * percent-encoded.
654
     *
655
     * @param  string $path The raw uri path.
656
     *
657
     * @return string The RFC 3986 percent-encoded uri path.
658
     *
659
     * @link   http://www.faqs.org/rfcs/rfc3986.html
660
     */
661 72
    private function urlEncode($path)
662
    {
663 72
        return preg_replace_callback(
664 72
            '/(?:[^a-zA-Z0-9_\-\.~:@&=\+\$,\/;%]+|%(?![A-Fa-f0-9]{2}))/',
665 72
            function ($match) {
666 1
                return rawurlencode($match[0]);
667 72
            },
668
            $path
669 72
        );
670
    }
671
}
672