Passed
Push — master ( 6c6323...3436a5 )
by Akmal
57s
created

Uri::withHost()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
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->getBasePart() .
455
            $this->getTailPart();
456
    }
457
458
    /**
459
     * Return an empty string or [scheme://][user:password@]host[:port]
460
     *
461
     * @return string
462
     */
463
    private function getBasePart(): string
464
    {
465
        $scheme = empty($this->getScheme()) ? '' : $this->getScheme() . ':';
466
        $authority = empty($this->getAuthority()) ? '' : '//' . $this->getAuthority();
467
468
        return $scheme . $authority;
469
    }
470
471
    /**
472
     * Return an empty string or /[path][?query][#fragment]
473
     *
474
     * @return string
475
     */
476
    private function getTailPart(): string
477
    {
478
        $path = '/';
479
480
        if (!empty($this->getPath())) {
481
            $path = strpos($this->getPath(), '/') === 0 ? '' : '/';
482
            $path .= $this->getPath();
483
        }
484
485
        $query = empty($this->getQuery()) ? '' : '?' . $this->getQuery();
486
        $fragment = empty($this->getFragment()) ? '' : '#' . $this->getFragment();
487
488
        return $path . $query . $fragment;
489
    }
490
491
    /**
492
     * @return bool
493
     */
494
    private function isDefaultPort(): bool
495
    {
496
        if ($this->port === null) {
497
            return true;
498
        }
499
500
        return
501
            isset($this->defaultPorts[$this->getScheme()]) &&
502
            $this->port === $this->defaultPorts[$this->getScheme()];
503
    }
504
505
    /**
506
     * @return string
507
     */
508
    private function getPassword(): string
509
    {
510
        return $this->password;
511
    }
512
513
    /**
514
     * @param string $scheme
515
     * @return Uri
516
     */
517
    private function setScheme(?string $scheme): Uri
518
    {
519
        if ($scheme !== null) {
520
            $this->scheme = strtolower($scheme);
521
        }
522
523
        return $this;
524
    }
525
526
    /**
527
     * @param string $user
528
     * @return Uri
529
     */
530
    private function setUser(?string $user): Uri
531
    {
532
        $this->user = $user ?? '';
533
        return $this;
534
    }
535
536
    /**
537
     * @param string $host
538
     * @return Uri
539
     */
540
    private function setHost(?string $host): Uri
541
    {
542
        $this->host = $host ?? '';
543
        return $this;
544
    }
545
546
    /**
547
     * @param int|null $port
548
     * @return Uri
549
     */
550
    private function setPort(?int $port): Uri
551
    {
552
        $this->port = $port;
553
        return $this;
554
    }
555
556
    /**
557
     * @param string $path
558
     * @return Uri
559
     */
560
    private function setPath(?string $path): Uri
561
    {
562
        $this->path = $path ?? '';
563
        return $this;
564
    }
565
566
    /**
567
     * @param string $query
568
     * @return Uri
569
     */
570
    private function setQuery(?string $query): Uri
571
    {
572
        $this->query = $query ?? '';
573
        return $this;
574
    }
575
576
    /**
577
     * @param null|string $password
578
     * @return Uri
579
     */
580
    private function setPassword(?string $password): Uri
581
    {
582
        $this->password = $password ?? '';
583
        return $this;
584
    }
585
586
    /**
587
     * @param null|string $fragment
588
     * @return Uri
589
     */
590
    private function setFragment(?string $fragment): Uri
591
    {
592
        $this->fragment = $fragment ?? '';
593
        return $this;
594
    }
595
}
596