Passed
Push — master ( be21dc...6c6323 )
by Akmal
01:00
created

Uri::getHost()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace OpenEngine\Mika\Core\Components\Http\Message\Uri;
4
5
use Psr\Http\Message\UriInterface;
6
7
class Uri implements UriInterface
8
{
9
    /**
10
     * @var array
11
     */
12
    private $defaultPorts = [
13
        'ftp' => 21,
14
        'http' => 80,
15
        'https' => 443,
16
        'imap' => 143,
17
        'ldap' => 389,
18
        'nntp' => 119,
19
        'pop' => 110,
20
        'telnet' => 23,
21
    ];
22
23
    /**
24
     * @var string
25
     */
26
    private $scheme;
27
28
    /**
29
     * @var string
30
     */
31
    private $user;
32
33
    /**
34
     * @var string
35
     */
36
    private $host;
37
38
    /**
39
     * @var int|null
40
     */
41
    private $port;
42
43
    /**
44
     * @var string
45
     */
46
    private $path;
47
48
    /**
49
     * @var string
50
     */
51
    private $query;
52
53
    /**
54
     * @var string
55
     */
56
    private $password;
57
58
    /**
59
     * @var string
60
     */
61
    private $fragment;
62
63
    /**
64
     * Uri constructor.
65
     * @param null|string $uri
66
     */
67
    public function __construct(?string $uri = null)
68
    {
69
        if ($uri === null) {
70
            return;
71
        }
72
73
        $this
74
            ->setScheme(parse_url($uri, PHP_URL_SCHEME))
75
            ->setUser(parse_url($uri, PHP_URL_USER))
76
            ->setPassword(parse_url($uri, PHP_URL_PASS))
77
            ->setHost(parse_url($uri, PHP_URL_HOST))
78
            ->setPort(parse_url($uri, PHP_URL_PORT))
0 ignored issues
show
Bug introduced by
parse_url($uri, OpenEngi...ssage\Uri\PHP_URL_PORT) of type string is incompatible with the type null|integer expected by parameter $port of OpenEngine\Mika\Core\Com...sage\Uri\Uri::setPort(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

78
            ->setPort(/** @scrutinizer ignore-type */ parse_url($uri, PHP_URL_PORT))
Loading history...
79
            ->setPath(parse_url($uri, PHP_URL_PATH))
80
            ->setQuery(parse_url($uri, PHP_URL_QUERY))
81
            ->setFragment(parse_url($uri, PHP_URL_FRAGMENT));
82
    }
83
84
    /**
85
     * Retrieve the scheme component of the URI.
86
     *
87
     * If no scheme is present, this method MUST return an empty string.
88
     *
89
     * The value returned MUST be normalized to lowercase, per RFC 3986
90
     * Section 3.1.
91
     *
92
     * The trailing ":" character is not part of the scheme and MUST NOT be
93
     * added.
94
     *
95
     * @see https://tools.ietf.org/html/rfc3986#section-3.1
96
     * @return string The URI scheme.
97
     */
98
    public function getScheme(): string
99
    {
100
        return $this->scheme;
101
    }
102
103
    /**
104
     * Retrieve the authority component of the URI.
105
     *
106
     * If no authority information is present, this method MUST return an empty
107
     * string.
108
     *
109
     * The authority syntax of the URI is:
110
     *
111
     * <pre>
112
     * [user-info@]host[:port]
113
     * </pre>
114
     *
115
     * If the port component is not set or is the standard port for the current
116
     * scheme, it SHOULD NOT be included.
117
     *
118
     * @see https://tools.ietf.org/html/rfc3986#section-3.2
119
     * @return string The URI authority, in "[user-info@]host[:port]" format.
120
     */
121
    public function getAuthority(): string
122
    {
123
        $result = '';
124
125
        if (!empty($this->getUserInfo())) {
126
            $result .= $this->getUserInfo() . '@';
127
        }
128
129
        $result .= $this->getHost();
130
        $result .= $this->getPort() === null ? '' : ':' . $this->getPort();
131
132
        return $result;
133
    }
134
135
    /**
136
     * Retrieve the user information component of the URI.
137
     *
138
     * If no user information is present, this method MUST return an empty
139
     * string.
140
     *
141
     * If a user is present in the URI, this will return that value;
142
     * additionally, if the password is also present, it will be appended to the
143
     * user value, with a colon (":") separating the values.
144
     *
145
     * The trailing "@" character is not part of the user information and MUST
146
     * NOT be added.
147
     *
148
     * @return string The URI user information, in "username[:password]" format.
149
     */
150
    public function getUserInfo(): string
151
    {
152
        $result = $this->user;
153
154
        if (!empty($this->getPassword())) {
155
            $result .= ':' . $this->getPassword();
156
        }
157
158
        return $result;
159
    }
160
161
    /**
162
     * Retrieve the host component of the URI.
163
     *
164
     * If no host is present, this method MUST return an empty string.
165
     *
166
     * The value returned MUST be normalized to lowercase, per RFC 3986
167
     * Section 3.2.2.
168
     *
169
     * @see http://tools.ietf.org/html/rfc3986#section-3.2.2
170
     * @return string The URI host.
171
     */
172
    public function getHost(): string
173
    {
174
        return $this->host;
175
    }
176
177
    /**
178
     * Retrieve the port component of the URI.
179
     *
180
     * If a port is present, and it is non-standard for the current scheme,
181
     * this method MUST return it as an integer. If the port is the standard port
182
     * used with the current scheme, this method SHOULD return null.
183
     *
184
     * If no port is present, and no scheme is present, this method MUST return
185
     * a null value.
186
     *
187
     * If no port is present, but a scheme is present, this method MAY return
188
     * the standard port for that scheme, but SHOULD return null.
189
     *
190
     * @return null|int The URI port.
191
     */
192
    public function getPort(): ?int
193
    {
194
        if ($this->isDefaultPort()) {
195
            return null;
196
        }
197
198
        return $this->port;
199
    }
200
201
    /**
202
     * Retrieve the path component of the URI.
203
     *
204
     * The path can either be empty or absolute (starting with a slash) or
205
     * rootless (not starting with a slash). Implementations MUST support all
206
     * three syntaxes.
207
     *
208
     * Normally, the empty path "" and absolute path "/" are considered equal as
209
     * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically
210
     * do this normalization because in contexts with a trimmed base path, e.g.
211
     * the front controller, this difference becomes significant. It's the task
212
     * of the user to handle both "" and "/".
213
     *
214
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
215
     * any characters. To determine what characters to encode, please refer to
216
     * RFC 3986, Sections 2 and 3.3.
217
     *
218
     * As an example, if the value should include a slash ("/") not intended as
219
     * delimiter between path segments, that value MUST be passed in encoded
220
     * form (e.g., "%2F") to the instance.
221
     *
222
     * @see https://tools.ietf.org/html/rfc3986#section-2
223
     * @see https://tools.ietf.org/html/rfc3986#section-3.3
224
     * @return string The URI path.
225
     */
226
    public function getPath(): string
227
    {
228
        return $this->path;
229
    }
230
231
    /**
232
     * Retrieve the query string of the URI.
233
     *
234
     * If no query string is present, this method MUST return an empty string.
235
     *
236
     * The leading "?" character is not part of the query and MUST NOT be
237
     * added.
238
     *
239
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
240
     * any characters. To determine what characters to encode, please refer to
241
     * RFC 3986, Sections 2 and 3.4.
242
     *
243
     * As an example, if a value in a key/value pair of the query string should
244
     * include an ampersand ("&") not intended as a delimiter between values,
245
     * that value MUST be passed in encoded form (e.g., "%26") to the instance.
246
     *
247
     * @see https://tools.ietf.org/html/rfc3986#section-2
248
     * @see https://tools.ietf.org/html/rfc3986#section-3.4
249
     * @return string The URI query string.
250
     */
251
    public function getQuery(): string
252
    {
253
        return $this->query;
254
    }
255
256
    /**
257
     * Retrieve the fragment component of the URI.
258
     *
259
     * If no fragment is present, this method MUST return an empty string.
260
     *
261
     * The leading "#" character is not part of the fragment and MUST NOT be
262
     * added.
263
     *
264
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
265
     * any characters. To determine what characters to encode, please refer to
266
     * RFC 3986, Sections 2 and 3.5.
267
     *
268
     * @see https://tools.ietf.org/html/rfc3986#section-2
269
     * @see https://tools.ietf.org/html/rfc3986#section-3.5
270
     * @return string The URI fragment.
271
     */
272
    public function getFragment(): string
273
    {
274
        return $this->fragment;
275
    }
276
277
    /**
278
     * Return an instance with the specified scheme.
279
     *
280
     * This method MUST retain the state of the current instance, and return
281
     * an instance that contains the specified scheme.
282
     *
283
     * Implementations MUST support the schemes "http" and "https" case
284
     * insensitively, and MAY accommodate other schemes if required.
285
     *
286
     * An empty scheme is equivalent to removing the scheme.
287
     *
288
     * @param string $scheme The scheme to use with the new instance.
289
     * @return static A new instance with the specified scheme.
290
     * @throws \InvalidArgumentException for invalid or unsupported schemes.
291
     */
292
    public function withScheme($scheme)
293
    {
294
        $clone = clone $this;
295
        return $clone->setScheme($scheme);
296
    }
297
298
    /**
299
     * Return an instance with the specified user information.
300
     *
301
     * This method MUST retain the state of the current instance, and return
302
     * an instance that contains the specified user information.
303
     *
304
     * Password is optional, but the user information MUST include the
305
     * user; an empty string for the user is equivalent to removing user
306
     * information.
307
     *
308
     * @param string $user The user name to use for authority.
309
     * @param null|string $password The password associated with $user.
310
     * @return static A new instance with the specified user information.
311
     */
312
    public function withUserInfo($user, $password = null)
313
    {
314
        $clone = clone $this;
315
        return $clone->setUser($user)->setPassword($password);
316
    }
317
318
    /**
319
     * Return an instance with the specified host.
320
     *
321
     * This method MUST retain the state of the current instance, and return
322
     * an instance that contains the specified host.
323
     *
324
     * An empty host value is equivalent to removing the host.
325
     *
326
     * @param string $host The hostname to use with the new instance.
327
     * @return static A new instance with the specified host.
328
     * @throws \InvalidArgumentException for invalid hostnames.
329
     */
330
    public function withHost($host)
331
    {
332
        $clone = clone $this;
333
        return $clone->setHost($host);
334
    }
335
336
    /**
337
     * Return an instance with the specified port.
338
     *
339
     * This method MUST retain the state of the current instance, and return
340
     * an instance that contains the specified port.
341
     *
342
     * Implementations MUST raise an exception for ports outside the
343
     * established TCP and UDP port ranges.
344
     *
345
     * A null value provided for the port is equivalent to removing the port
346
     * information.
347
     *
348
     * @param null|int $port The port to use with the new instance; a null value
349
     *     removes the port information.
350
     * @return static A new instance with the specified port.
351
     * @throws \InvalidArgumentException for invalid ports.
352
     */
353
    public function withPort($port)
354
    {
355
        $clone = clone $this;
356
        return $clone->setPort($port);
357
    }
358
359
    /**
360
     * Return an instance with the specified path.
361
     *
362
     * This method MUST retain the state of the current instance, and return
363
     * an instance that contains the specified path.
364
     *
365
     * The path can either be empty or absolute (starting with a slash) or
366
     * rootless (not starting with a slash). Implementations MUST support all
367
     * three syntaxes.
368
     *
369
     * If the path is intended to be domain-relative rather than path relative then
370
     * it must begin with a slash ("/"). Paths not starting with a slash ("/")
371
     * are assumed to be relative to some base path known to the application or
372
     * consumer.
373
     *
374
     * Users can provide both encoded and decoded path characters.
375
     * Implementations ensure the correct encoding as outlined in getPath().
376
     *
377
     * @param string $path The path to use with the new instance.
378
     * @return static A new instance with the specified path.
379
     * @throws \InvalidArgumentException for invalid paths.
380
     */
381
    public function withPath($path)
382
    {
383
        $clone = clone $this;
384
        return $clone->setPath($path);
385
    }
386
387
    /**
388
     * Return an instance with the specified query string.
389
     *
390
     * This method MUST retain the state of the current instance, and return
391
     * an instance that contains the specified query string.
392
     *
393
     * Users can provide both encoded and decoded query characters.
394
     * Implementations ensure the correct encoding as outlined in getQuery().
395
     *
396
     * An empty query string value is equivalent to removing the query string.
397
     *
398
     * @param string $query The query string to use with the new instance.
399
     * @return static A new instance with the specified query string.
400
     * @throws \InvalidArgumentException for invalid query strings.
401
     */
402
    public function withQuery($query)
403
    {
404
        $clone = clone $this;
405
        return $clone->setQuery($query);
406
    }
407
408
    /**
409
     * Return an instance with the specified URI fragment.
410
     *
411
     * This method MUST retain the state of the current instance, and return
412
     * an instance that contains the specified URI fragment.
413
     *
414
     * Users can provide both encoded and decoded fragment characters.
415
     * Implementations ensure the correct encoding as outlined in getFragment().
416
     *
417
     * An empty fragment value is equivalent to removing the fragment.
418
     *
419
     * @param string $fragment The fragment to use with the new instance.
420
     * @return static A new instance with the specified fragment.
421
     */
422
    public function withFragment($fragment)
423
    {
424
        $clone = clone $this;
425
        return $clone->setFragment($fragment);
426
    }
427
428
    /**
429
     * Return the string representation as a URI reference.
430
     *
431
     * Depending on which components of the URI are present, the resulting
432
     * string is either a full URI or relative reference according to RFC 3986,
433
     * Section 4.1. The method concatenates the various components of the URI,
434
     * using the appropriate delimiters:
435
     *
436
     * - If a scheme is present, it MUST be suffixed by ":".
437
     * - If an authority is present, it MUST be prefixed by "//".
438
     * - The path can be concatenated without delimiters. But there are two
439
     *   cases where the path has to be adjusted to make the URI reference
440
     *   valid as PHP does not allow to throw an exception in __toString():
441
     *     - If the path is rootless and an authority is present, the path MUST
442
     *       be prefixed by "/".
443
     *     - If the path is starting with more than one "/" and no authority is
444
     *       present, the starting slashes MUST be reduced to one.
445
     * - If a query is present, it MUST be prefixed by "?".
446
     * - If a fragment is present, it MUST be prefixed by "#".
447
     *
448
     * @see http://tools.ietf.org/html/rfc3986#section-4.1
449
     * @return string
450
     */
451
    public function __toString()
452
    {
453
        return
454
            $this->getSchemeWithColon() .
455
            $this->getAuthorityWithDoubleSlash() .
456
            $this->getPathWithSlash() .
457
            $this->getQueryWithMark() .
458
            $this->getFragmentWithSharp();
459
    }
460
461
    /**
462
     * Returns empty string if scheme is not set or ":scheme"
463
     *
464
     * @return string
465
     */
466
    private function getSchemeWithColon(): string
467
    {
468
        return empty($this->getScheme()) ? '' : $this->getScheme() . ':';
469
    }
470
471
    /**
472
     * Returns empty string if authority is not set or "//authority"
473
     *
474
     * @return string
475
     */
476
    private function getAuthorityWithDoubleSlash(): string
477
    {
478
        return empty($this->getAuthority()) ? '' : '//' . $this->getAuthority();
479
    }
480
481
    /**
482
     * Returns empty string if path is not set or "/path"
483
     *
484
     * @return string
485
     */
486
    private function getPathWithSlash(): string
487
    {
488
        $path = '';
489
490
        if (!empty($this->getPath())) {
491
            $path .= strpos($this->getPath(), '/') === 0 ? '' : '/';
492
            $path .= $this->getPath();
493
        }
494
495
        return $path;
496
    }
497
498
    /**
499
     * Returns empty string if query is not set or "?query"
500
     *
501
     * @return string
502
     */
503
    private function getQueryWithMark(): string
504
    {
505
        return empty($this->getQuery()) ? '' : '?' . $this->getQuery();
506
    }
507
508
    /**
509
     * Returns empty string if fragment is not set or "#fragment"
510
     *
511
     * @return string
512
     */
513
    private function getFragmentWithSharp(): string
514
    {
515
        return empty($this->getFragment()) ? '' : '#' . $this->getFragment();
516
    }
517
518
    /**
519
     * @return bool
520
     */
521
    private function isDefaultPort(): bool
522
    {
523
        if ($this->port === null) {
524
            return true;
525
        }
526
527
        return
528
            isset($this->defaultPorts[$this->getScheme()]) &&
529
            $this->port === $this->defaultPorts[$this->getScheme()];
530
    }
531
532
    /**
533
     * @return string
534
     */
535
    private function getPassword(): string
536
    {
537
        return $this->password;
538
    }
539
540
    /**
541
     * @param string $scheme
542
     * @return Uri
543
     */
544
    private function setScheme(?string $scheme): Uri
545
    {
546
        if ($scheme !== null) {
547
            $this->scheme = strtolower($scheme);
548
        }
549
550
        return $this;
551
    }
552
553
    /**
554
     * @param string $user
555
     * @return Uri
556
     */
557
    private function setUser(?string $user): Uri
558
    {
559
        $this->user = $user ?? '';
560
        return $this;
561
    }
562
563
    /**
564
     * @param string $host
565
     * @return Uri
566
     */
567
    private function setHost(?string $host): Uri
568
    {
569
        $this->host = $host ?? '';
570
        return $this;
571
    }
572
573
    /**
574
     * @param int|null $port
575
     * @return Uri
576
     */
577
    private function setPort(?int $port): Uri
578
    {
579
        $this->port = $port;
580
        return $this;
581
    }
582
583
    /**
584
     * @param string $path
585
     * @return Uri
586
     */
587
    private function setPath(?string $path): Uri
588
    {
589
        $this->path = $path ?? '';
590
        return $this;
591
    }
592
593
    /**
594
     * @param string $query
595
     * @return Uri
596
     */
597
    private function setQuery(?string $query): Uri
598
    {
599
        $this->query = $query ?? '';
600
        return $this;
601
    }
602
603
    /**
604
     * @param null|string $password
605
     * @return Uri
606
     */
607
    private function setPassword(?string $password): Uri
608
    {
609
        $this->password = $password ?? '';
610
        return $this;
611
    }
612
613
    /**
614
     * @param null|string $fragment
615
     * @return Uri
616
     */
617
    private function setFragment(?string $fragment): Uri
618
    {
619
        $this->fragment = $fragment ?? '';
620
        return $this;
621
    }
622
}
623