GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Uri::withPort()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 6
rs 10
1
<?php
2
/**
3
 * This file is part of the O2System Framework package.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 *
8
 * @author         Steeve Andrian Salim
9
 * @copyright      Copyright (c) Steeve Andrian Salim
10
 */
11
12
// ------------------------------------------------------------------------
13
14
namespace O2System\Kernel\Http\Message;
15
16
// ------------------------------------------------------------------------
17
18
use O2System\Kernel\Http\Message\Uri\Segments;
19
use O2System\Psr\Http\Message\UriInterface;
20
21
/**
22
 * Class Uri
23
 *
24
 * @package O2System\Kernel\Http
25
 */
26
class Uri implements UriInterface
27
{
28
    /**
29
     * Uri::$scheme
30
     *
31
     * @var string
32
     */
33
    protected $scheme;
34
35
    /**
36
     * Uri::$segments
37
     *
38
     * @var \O2System\Kernel\Http\Message\Uri\Segments
39
     */
40
    public $segments;
41
42
    /**
43
     * Uri::$suffix
44
     *
45
     * @var string
46
     */
47
    protected $suffix;
48
49
    /**
50
     * Uri::$host
51
     *
52
     * The host subcomponent of authority is identified by an IP literal
53
     * encapsulated within square brackets, an IPv4 address in dotted-decimal form,
54
     * or a registered name.
55
     *
56
     * @see https://tools.ietf.org/html/rfc3986
57
     *
58
     * @var string  IP-literal / IPv4address / registered-name
59
     */
60
    protected $host;
61
62
    /**
63
     * Uri::$port
64
     *
65
     * @var int
66
     */
67
    protected $port = 80;
68
69
    /**
70
     * Uri::$username
71
     *
72
     * @var string
73
     */
74
    protected $username;
75
76
    /**
77
     * Uri::$password
78
     *
79
     * @var string
80
     */
81
    protected $password;
82
83
    /**
84
     * Uri::$path
85
     *
86
     * @var string
87
     */
88
    protected $path;
89
90
    /**
91
     * Uri::$query
92
     *
93
     * @var string
94
     */
95
    protected $query;
96
97
    /**
98
     * Uri::$fragment
99
     *
100
     * @var string
101
     */
102
    protected $fragment;
103
104
    /**
105
     * Uri::$attribute
106
     *
107
     * @var string
108
     */
109
    protected $attribute;
110
111
    /**
112
     * Uri::$subDomain
113
     *
114
     * @var string
115
     */
116
    protected $subDomain;
117
118
    /**
119
     * Uri::$subDomains
120
     *
121
     * List of Uri SubDomains
122
     *
123
     * @var array
124
     */
125
    protected $subDomains = [];
126
127
    /**
128
     * Uri::$tld
129
     *
130
     * Uri Top Level Domain
131
     *
132
     * @var string
133
     */
134
    protected $tld;
135
136
    /**
137
     * Uri::$tlds
138
     *
139
     * List of Uri Top Level Domains
140
     *
141
     * @var array
142
     */
143
    protected $tlds = [];
144
145
    // ------------------------------------------------------------------------
146
147
    /**
148
     * Uri::__construct
149
     *
150
     * @param string|null $httpStringRequest
151
     */
152
    public function __construct($httpStringRequest = null)
153
    {
154
        if (isset($httpStringRequest)) {
155
            $this->segments = new Segments('');
156
            $httpStringRequest = ltrim($httpStringRequest, '//');
157
158
            if (strpos($httpStringRequest, 'http://') === false) {
159
                if (strpos($httpStringRequest, 'https://') === false) {
160
                    $httpStringRequest = 'http://' . $httpStringRequest;
161
                }
162
            }
163
164
            $httpStringRequest = trim($httpStringRequest, '/');
165
            $parseUrl = parse_url($httpStringRequest);
166
167
            $this->host = isset($parseUrl[ 'host' ]) ? $parseUrl[ 'host' ] : null;
168
169
            $this->scheme = isset($parseUrl[ 'scheme' ]) ? $parseUrl[ 'scheme' ] : (is_https() ? 'https' : 'http');
170
171
            /**
172
             * Define Uri Port
173
             */
174
            if (strpos($this->scheme, 'https') !== false) {
175
                $this->port = 443;
176
            }
177
178
            if (isset($parseUrl[ 'path' ])) {
179
                $xRequest = explode('/', $parseUrl[ 'path' ]);
180
                $this->path = implode('/', array_slice($xRequest, 1));
181
            }
182
183
            if (strpos($this->path, '.php') !== false) {
184
                $xPath = explode('.php', $this->path);
185
                $xPath = explode('/', trim($xPath[ 0 ], '/'));
186
                array_pop($xPath);
187
188
                $this->path = empty($xPath) ? null : implode('/', $xPath);
189
            }
190
191
            $this->query = isset($parseUrl[ 'query' ]) ? $parseUrl[ 'query' ] : null;
192
            $this->username = isset($parseUrl[ 'user' ]) ? $parseUrl[ 'user' ] : null;
193
            $this->password = isset($parseUrl[ 'pass' ]) ? $parseUrl[ 'pass' ] : null;
194
            $this->port = isset($parseUrl[ 'port' ]) ? $parseUrl[ 'port' ] : 80;
195
            $this->fragment = isset($parseUrl[ 'fragment' ]) ? $parseUrl[ 'fragment' ] : null;
196
        } else {
197
            $this->segments = new Segments();
198
199
            /**
200
             * Define Uri Host
201
             */
202
            $this->host = isset($_SERVER[ 'HTTP_HOST' ])
203
                ? str_replace('www.', '', $_SERVER[ 'HTTP_HOST' ])
204
                : str_replace('www.', '', $_SERVER[ 'SERVER_NAME' ]);
205
206
            /**
207
             * Define Uri Scheme
208
             */
209
            $this->scheme = is_https() ? 'https' : 'http';
210
211
            /**
212
             * Define Uri Attribute
213
             */
214
            if (strpos($_SERVER[ 'PHP_SELF' ], '/@') !== false) {
215
                $xPhpSelf = explode('/@', $_SERVER[ 'PHP_SELF' ]);
216
217
                $this->attribute = '@' . $xPhpSelf[ 1 ];
218
219
                if (strpos($this->attribute, '/') !== false) {
220
                    $xAttribute = explode('/', $this->attribute);
221
222
                    $this->attribute = $xAttribute[ 0 ];
223
                }
224
            }
225
226
            /**
227
             * Define Uri User and Password
228
             */
229
            if (preg_match("/[a-zA-Z0-9]+[@][a-zA-Z0-9]+/", $_SERVER[ 'PHP_SELF' ], $usernamePassword)) {
230
                $xUsernamePassword = explode('@', $usernamePassword[ 0 ]);
231
                $this->username = $xUsernamePassword[ 0 ];
232
                $this->password = $xUsernamePassword[ 1 ];
233
            }
234
235
            /**
236
             * Define Uri Port
237
             */
238
            $this->port = is_https() ? 443 : 80;
239
240
            if (strpos($this->host, ':') !== false) {
241
                $xHost = explode(':', $this->host);
242
                $this->host = reset($xHost);
243
                $this->port = end($xHost);
244
            }
245
246
            /**
247
             * Define Uri Path
248
             */
249
            $xPath = explode('.php', $_SERVER[ 'PHP_SELF' ]);
250
            $xPath = explode('/', trim($xPath[ 0 ], '/'));
251
            array_pop($xPath);
252
253
            $this->path = empty($xPath) ? null : implode('/', $xPath) . '/';
254
255
            $this->query = isset($_SERVER[ 'QUERY_STRING' ]) ? $_SERVER[ 'QUERY_STRING' ] : null;
256
257
        }
258
259
        if (filter_var($this->host, FILTER_VALIDATE_IP) !== false OR strpos($this->host, '.') === false) {
260
            $xHost = [$this->host];
261
        } else {
262
            $xHost = explode('.', str_replace('www.', '', $this->host));
263
        }
264
265
        /**
266
         * Define Uri Tld
267
         */
268
        if ($xHostNum = count($xHost)) {
0 ignored issues
show
Unused Code introduced by
The assignment to $xHostNum is dead and can be removed.
Loading history...
269
            $this->tlds[] = end($xHost);
270
            array_pop($xHost);
271
272
            if (count($xHost) >= 2) {
273
                $this->subDomains[] = $this->subDomain = reset($xHost);
274
                array_shift($xHost);
275
276
                if (count($xHost)) {
277
                    if (strlen($tld = end($xHost)) <= 3) {
278
                        array_unshift($this->tlds, $tld);
279
                        array_pop($xHost);
280
                    }
281
                }
282
            }
283
284
            if (count($xHost)) {
285
                $this->host = implode('.', $xHost) . '.' . implode('.', $this->tlds);
286
            }
287
288
            $this->tld = '.' . implode('.', $this->tlds);
289
        }
290
291
        $xHost = explode('.', $this->host);
292
        $xHostNum = count($xHost);
293
        $tldsNum = count($this->tlds);
294
295
        // Convert Keys to Ordinal
296
        $this->setOrdinalKeys($this->subDomains, ($tldsNum + $xHostNum) - 1);
297
        $this->setOrdinalKeys($this->tlds, $tldsNum);
298
299
        if (services()->has('config')) {
300
            if (config()->offsetExists('uri')) {
0 ignored issues
show
Bug introduced by
The function config was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

300
            if (/** @scrutinizer ignore-call */ config()->offsetExists('uri')) {
Loading history...
301
                $this->setSuffix(config('uri')->offsetGet('suffix'));
302
            }
303
        }
304
    }
305
306
    // ------------------------------------------------------------------------
307
308
    /**
309
     * Uri::setOrdinalKeys
310
     *
311
     * @param array $elements
312
     * @param int   $startNumber
313
     */
314
    protected function setOrdinalKeys(array &$elements, $startNumber = 0)
315
    {
316
        $ordinalEnds = ['th', 'st', 'nd', 'rd', 'th', 'th', 'th', 'th', 'th', 'th'];
317
318
        foreach ($elements as $key => $subdomain) {
319
            $ordinalNumber = $startNumber - intval($key);
320
321
            if ((($ordinalNumber % 100) >= 11) && (($ordinalNumber % 100) <= 13)) {
322
                $ordinalKey = $ordinalNumber . 'th';
323
            } else {
324
                $ordinalKey = $ordinalNumber . $ordinalEnds[ $ordinalNumber % 10 ];
325
            }
326
327
            $elements[ $ordinalKey ] = $subdomain;
328
329
            unset($elements[ $key ]);
330
        }
331
    }
332
333
    // ------------------------------------------------------------------------
334
335
    /**
336
     * Uri::getSegments
337
     *
338
     * @return Segments
339
     */
340
    public function &getSegments()
341
    {
342
        return $this->segments;
343
    }
344
345
    // ------------------------------------------------------------------------
346
347
    /**
348
     * Uri::withSegments
349
     *
350
     * @param Segments $segments
351
     *
352
     * @return Uri
353
     */
354
    public function withSegments(Segments $segments)
355
    {
356
        $uri = clone $this;
357
        $uri->segments = $segments;
358
359
        return $uri;
360
    }
361
362
    // ------------------------------------------------------------------------
363
364
    /**
365
     * Uri::addSegments
366
     *
367
     * @param Segments|string|array $segments
368
     *
369
     * @return \O2System\Kernel\Http\Message\Uri
370
     * @throws \O2System\Spl\Exceptions\RuntimeException
371
     */
372
    public function addSegments($segments)
373
    {
374
        if ( ! $segments instanceof Segments) {
375
            $segments = new Segments($segments);
0 ignored issues
show
Bug introduced by
It seems like $segments can also be of type array; however, parameter $segments of O2System\Kernel\Http\Mes...Segments::__construct() does only seem to accept null|string, maybe add an additional type check? ( Ignorable by Annotation )

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

375
            $segments = new Segments(/** @scrutinizer ignore-type */ $segments);
Loading history...
376
        }
377
378
        $currentSegments = $this->segments->getArrayCopy();
379
        $addSegments = $segments->getArrayCopy();
380
381
        $uri = clone $this;
382
        $uri->segments = new Segments(array_merge($currentSegments, $addSegments));
383
384
        return $uri;
385
    }
386
387
    // ------------------------------------------------------------------------
388
389
    /**
390
     * Uri::getScheme
391
     *
392
     * Retrieve the scheme component of the URI.
393
     *
394
     * If no scheme is present, this method MUST return an empty string.
395
     *
396
     * The value returned MUST be normalized to lowercase, per RFC 3986
397
     * Section 3.1.
398
     *
399
     * The trailing ":" character is not part of the scheme and MUST NOT be
400
     * added.
401
     *
402
     * @see https://tools.ietf.org/html/rfc3986#section-3.1
403
     * @return string The URI scheme.
404
     */
405
    public function getScheme()
406
    {
407
        return $this->scheme;
408
    }
409
410
    // ------------------------------------------------------------------------
411
412
    /**
413
     * Uri::getAuthority
414
     *
415
     * Retrieve the authority component of the URI.
416
     *
417
     * If no authority information is present, this method MUST return an empty
418
     * string.
419
     *
420
     * The authority syntax of the URI is:
421
     *
422
     * <pre>
423
     * [user-info@]host[:port]
424
     * </pre>
425
     *
426
     * If the port component is not set or is the standard port for the current
427
     * scheme, it SHOULD NOT be included.
428
     *
429
     * @see https://tools.ietf.org/html/rfc3986#section-3.2
430
     * @return string The URI authority, in "[user-info@]host[:port]" format.
431
     */
432
    public function getAuthority()
433
    {
434
        if (empty($this->host)) {
435
            return null;
436
        }
437
438
        $authority = $this->host;
439
440
        if ( ! empty($this->getUserInfo())) {
441
            $authority = $this->getUserInfo() . '@' . $authority;
442
        }
443
444
        if ( ! empty($this->port)) {
445
            if ($this->port != 80) {
446
                $authority .= ':' . $this->port;
447
            }
448
        }
449
450
        return $authority;
451
    }
452
453
    // ------------------------------------------------------------------------
454
455
    /**
456
     * Uri::getUserInfo
457
     *
458
     * Retrieve the user information component of the URI.
459
     *
460
     * If no user information is present, this method MUST return an empty
461
     * string.
462
     *
463
     * If a user is present in the URI, this will return that value;
464
     * additionally, if the password is also present, it will be appended to the
465
     * user value, with a colon (":") separating the values.
466
     *
467
     * The trailing "@" character is not part of the user information and MUST
468
     * NOT be added.
469
     *
470
     * @return string The URI user information, in "username[:password]" format.
471
     */
472
    public function getUserInfo()
473
    {
474
        $userInfo = $this->username;
475
476
        if ( ! empty($this->password)) {
477
            $userInfo .= ':' . $this->password;
478
        }
479
480
        return $userInfo;
481
    }
482
483
    // ------------------------------------------------------------------------
484
485
    /**
486
     * Uri::getHost
487
     *
488
     * Retrieve the host component of the URI.
489
     *
490
     * If no host is present, this method MUST return an empty string.
491
     *
492
     * The value returned MUST be normalized to lowercase, per RFC 3986
493
     * Section 3.2.2.
494
     *
495
     * @see http://tools.ietf.org/html/rfc3986#section-3.2.2
496
     * @return string The URI host.
497
     */
498
    public function getHost()
499
    {
500
        return $this->host;
501
    }
502
503
    // ------------------------------------------------------------------------
504
505
    /**
506
     * Uri::getPort
507
     *
508
     * Retrieve the port component of the URI.
509
     *
510
     * If a port is present, and it is non-standard for the current scheme,
511
     * this method MUST return it as an integer. If the port is the standard port
512
     * used with the current scheme, this method SHOULD return null.
513
     *
514
     * If no port is present, and no scheme is present, this method MUST return
515
     * a null value.
516
     *
517
     * If no port is present, but a scheme is present, this method MAY return
518
     * the standard port for that scheme, but SHOULD return null.
519
     *
520
     * @return null|int The URI port.
521
     */
522
    public function getPort()
523
    {
524
        return $this->port;
525
    }
526
527
    // ------------------------------------------------------------------------
528
529
    /**
530
     * Uri::getPath
531
     *
532
     * Retrieve the path component of the URI.
533
     *
534
     * The path can either be empty or absolute (starting with a slash) or
535
     * rootless (not starting with a slash). Implementations MUST support all
536
     * three syntaxes.
537
     *
538
     * Normally, the empty path "" and absolute path "/" are considered equal as
539
     * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically
540
     * do this normalization because in contexts with a trimmed base path, e.g.
541
     * the front controller, this difference becomes significant. It's the task
542
     * of the user to handle both "" and "/".
543
     *
544
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
545
     * any characters. To determine what characters to encode, please refer to
546
     * RFC 3986, Sections 2 and 3.3.
547
     *
548
     * As an example, if the value should include a slash ("/") not intended as
549
     * delimiter between path segments, that value MUST be passed in encoded
550
     * form (e.g., "%2F") to the instance.
551
     *
552
     * @see https://tools.ietf.org/html/rfc3986#section-2
553
     * @see https://tools.ietf.org/html/rfc3986#section-3.3
554
     * @return string The URI path.
555
     */
556
    public function &getPath()
557
    {
558
        return $this->path;
559
    }
560
561
    // ------------------------------------------------------------------------
562
563
    /**
564
     * Uri::getQuery
565
     *
566
     * Retrieve the query string of the URI.
567
     *
568
     * If no query string is present, this method MUST return an empty string.
569
     *
570
     * The leading "?" character is not part of the query and MUST NOT be
571
     * added.
572
     *
573
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
574
     * any characters. To determine what characters to encode, please refer to
575
     * RFC 3986, Sections 2 and 3.4.
576
     *
577
     * As an example, if a value in a key/value pair of the query string should
578
     * include an ampersand ("&") not intended as a delimiter between values,
579
     * that value MUST be passed in encoded form (e.g., "%26") to the instance.
580
     *
581
     * @see https://tools.ietf.org/html/rfc3986#section-2
582
     * @see https://tools.ietf.org/html/rfc3986#section-3.4
583
     * @return string The URI query string.
584
     */
585
    public function getQuery()
586
    {
587
        return $this->query;
588
    }
589
590
    // ------------------------------------------------------------------------
591
592
    /**
593
     * Uri::getFragment
594
     *
595
     * Retrieve the fragment component of the URI.
596
     *
597
     * If no fragment is present, this method MUST return an empty string.
598
     *
599
     * The leading "#" character is not part of the fragment and MUST NOT be
600
     * added.
601
     *
602
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
603
     * any characters. To determine what characters to encode, please refer to
604
     * RFC 3986, Sections 2 and 3.5.
605
     *
606
     * @see https://tools.ietf.org/html/rfc3986#section-2
607
     * @see https://tools.ietf.org/html/rfc3986#section-3.5
608
     * @return string The URI fragment.
609
     */
610
    public function getFragment()
611
    {
612
        return $this->fragment;
613
    }
614
615
    // ------------------------------------------------------------------------
616
617
    /**
618
     * Uri::getSubDomain
619
     *
620
     * @param string $level
621
     *
622
     * @return bool|mixed
623
     */
624
    public function getSubDomain($level = 'AUTO')
625
    {
626
        if ($level === 'AUTO') {
627
            return reset($this->subDomains);
628
        } elseif (isset($this->subDomains[ $level ])) {
629
            return $this->subDomains[ $level ];
630
        }
631
632
        return false;
633
    }
634
635
    // ------------------------------------------------------------------------
636
637
    /**
638
     * Uri::addSubDomain
639
     *
640
     * @param string|array|null $subDomain
641
     *
642
     * @return \O2System\Kernel\Http\Message\Uri
643
     */
644
    public function addSubDomain($subDomain)
645
    {
646
        $uri = clone $this;
647
648
        if (is_null($subDomain)) {
649
            $uri->subDomain = null;
650
            $uri->subDomains = [];
651
        } elseif (is_string($subDomain)) {
652
            $uri->subDomain = $subDomain;
653
            array_unshift($uri->subDomains, $subDomain);
654
        } elseif (is_array($subDomain)) {
0 ignored issues
show
introduced by
The condition is_array($subDomain) is always true.
Loading history...
655
            $uri->subDomain = reset($subDomain);
656
            $uri->subDomains = array_merge($uri->subDomains, $subDomain);
657
        }
658
659
        $uri->subDomains = array_unique($uri->subDomains);
660
        $this->setOrdinalKeys($uri->subDomains, count($uri->subDomains) + 2);
661
662
        return $uri;
663
    }
664
665
    // ------------------------------------------------------------------------
666
667
    /**
668
     * Uri::withSubDomain
669
     *
670
     * @param string|array|null $subDomain
671
     *
672
     * @return \O2System\Kernel\Http\Message\Uri
673
     */
674
    public function withSubDomain($subDomain)
675
    {
676
        $uri = clone $this;
677
678
        if (is_null($subDomain)) {
679
            $uri->subDomain = null;
680
            $uri->subDomains = [];
681
        } elseif (is_string($subDomain)) {
682
            $uri->subDomain = $subDomain;
683
            $uri->subDomains = [$subDomain];
684
        } elseif (is_array($subDomain)) {
0 ignored issues
show
introduced by
The condition is_array($subDomain) is always true.
Loading history...
685
            $uri->subDomain = reset($subDomain);
686
            $uri->subDomains = $subDomain;
687
        }
688
689
        $uri->subDomains = array_unique($uri->subDomains);
690
        $this->setOrdinalKeys($uri->subDomains, count($uri->subDomains) + 2);
691
692
        return $uri;
693
    }
694
695
    // ------------------------------------------------------------------------
696
697
    /**
698
     * Uri::getSubDomains
699
     *
700
     * @return array
701
     */
702
    public function getSubDomains()
703
    {
704
        return $this->subDomains;
705
    }
706
707
    // ------------------------------------------------------------------------
708
709
    /**
710
     * Uri::withSubDomains
711
     *
712
     * @param array $subDomains
713
     *
714
     * @return \O2System\Kernel\Http\Message\Uri
715
     */
716
    public function withSubDomains(array $subDomains)
717
    {
718
        $uri = clone $this;
719
        $uri->subDomain = reset($subDomains);
720
        $uri->subDomains = $subDomains;
721
722
        return $uri;
723
    }
724
725
    // ------------------------------------------------------------------------
726
727
    /**
728
     * Uri::withScheme
729
     *
730
     * Return an instance with the specified scheme.
731
     *
732
     * This method MUST retain the state of the current instance, and return
733
     * an instance that contains the specified scheme.
734
     *
735
     * Implementations MUST support the schemes "http" and "https" case
736
     * insensitively, and MAY accommodate other schemes if required.
737
     *
738
     * An empty scheme is equivalent to removing the scheme.
739
     *
740
     * @param string $scheme The scheme to use with the new instance.
741
     *
742
     * @return static|\O2System\Kernel\Http\Message\Uri A new instance with the specified scheme.
743
     * @throws \InvalidArgumentException for invalid schemes.
744
     * @throws \InvalidArgumentException for unsupported schemes.
745
     */
746
    public function withScheme($scheme)
747
    {
748
        $uri = clone $this;
749
750
        if (in_array($scheme, ['http', 'https'])) {
751
            $uri->scheme = $scheme;
752
        }
753
754
        return $uri;
755
    }
756
757
    // ------------------------------------------------------------------------
758
759
    /**
760
     * Uri::withUserInfo
761
     *
762
     * Return an instance with the specified user information.
763
     *
764
     * This method MUST retain the state of the current instance, and return
765
     * an instance that contains the specified user information.
766
     *
767
     * Password is optional, but the user information MUST include the
768
     * user; an empty string for the user is equivalent to removing user
769
     * information.
770
     *
771
     * @param string      $user     The user name to use for authority.
772
     * @param null|string $password The password associated with $user.
773
     *
774
     * @return static|\O2System\Kernel\Http\Message\Uri A new instance with the specified user information.
775
     */
776
    public function withUserInfo($user, $password = null)
777
    {
778
        $userInfo = clone $this;
779
        $userInfo->username = $user;
780
781
        return $userInfo;
782
    }
783
784
    // ------------------------------------------------------------------------
785
786
    /**
787
     * Uri::withHost
788
     *
789
     * Return an instance with the specified host.
790
     *
791
     * This method MUST retain the state of the current instance, and return
792
     * an instance that contains the specified host.
793
     *
794
     * An empty host value is equivalent to removing the host.
795
     *
796
     * @param string $host The hostname to use with the new instance.
797
     *
798
     * @return static|\O2System\Kernel\Http\Message\Uri A new instance with the specified host.
799
     * @throws \InvalidArgumentException for invalid hostnames.
800
     */
801
    public function withHost($host)
802
    {
803
        $uri = clone $this;
804
        $uri->host = $host;
805
806
        return $uri;
807
    }
808
809
    // ------------------------------------------------------------------------
810
811
    /**
812
     * Uri::withPort
813
     *
814
     * Return an instance with the specified port.
815
     *
816
     * This method MUST retain the state of the current instance, and return
817
     * an instance that contains the specified port.
818
     *
819
     * Implementations MUST raise an exception for ports outside the
820
     * established TCP and UDP port ranges.
821
     *
822
     * A null value provided for the port is equivalent to removing the port
823
     * information.
824
     *
825
     * @param null|int $port The port to use with the new instance; a null value
826
     *                       removes the port information.
827
     *
828
     * @return static|\O2System\Kernel\Http\Message\Uri A new instance with the specified port.
829
     * @throws \InvalidArgumentException for invalid ports.
830
     */
831
    public function withPort($port)
832
    {
833
        $uri = clone $this;
834
        $uri->port = $port;
835
836
        return $uri;
837
    }
838
839
    // ------------------------------------------------------------------------
840
841
    /**
842
     * Uri::withPath
843
     *
844
     * Return an instance with the specified path.
845
     *
846
     * This method MUST retain the state of the current instance, and return
847
     * an instance that contains the specified path.
848
     *
849
     * The path can either be empty or absolute (starting with a slash) or
850
     * rootless (not starting with a slash). Implementations MUST support all
851
     * three syntaxes.
852
     *
853
     * If an HTTP path is intended to be host-relative rather than path-relative
854
     * then it must begin with a slash ("/"). HTTP paths not starting with a slash
855
     * are assumed to be relative to some base path known to the application or
856
     * consumer.
857
     *
858
     * Users can provide both encoded and decoded path characters.
859
     * Implementations ensure the correct encoding as outlined in getPath().
860
     *
861
     * @param string $path The path to use with the new instance.
862
     *
863
     * @return static|\O2System\Kernel\Http\Message\Uri A new instance with the specified path.
864
     * @throws \InvalidArgumentException for invalid paths.
865
     */
866
    public function withPath($path)
867
    {
868
        $uri = clone $this;
869
        $uri->path = ltrim($path, '/');
870
871
        return $uri;
872
    }
873
874
    // ------------------------------------------------------------------------
875
876
    /**
877
     * Uri::addPath
878
     *
879
     * @param string $path
880
     *
881
     * @return \O2System\Kernel\Http\Message\Uri
882
     */
883
    public function addPath($path)
884
    {
885
        $uri = clone $this;
886
        $uri->path .= '/' . ltrim($path, '/');
887
888
        return $uri;
889
    }
890
891
    // ------------------------------------------------------------------------
892
893
    /**
894
     * Uri::withQuery
895
     *
896
     * Return an instance with the specified query string.
897
     *
898
     * This method MUST retain the state of the current instance, and return
899
     * an instance that contains the specified query string.
900
     *
901
     * Users can provide both encoded and decoded query characters.
902
     * Implementations ensure the correct encoding as outlined in getQuery().
903
     *
904
     * An empty query string value is equivalent to removing the query string.
905
     *
906
     * @param string|array $query The query string to use with the new instance.
907
     *
908
     * @return static|\O2System\Kernel\Http\Message\Uri A new instance with the specified query string.
909
     * @throws \InvalidArgumentException for invalid query strings.
910
     */
911
    public function withQuery($query)
912
    {
913
        $uri = clone $this;
914
        $uri->query = is_array($query) ? http_build_query($query, PHP_QUERY_RFC3986, '&', PHP_QUERY_RFC3986) : $query;
915
916
        return $uri;
917
    }
918
919
    // ------------------------------------------------------------------------
920
921
    /**
922
     * Uri::addQuery
923
     *
924
     * @param array|string $query
925
     *
926
     * @return \O2System\Kernel\Http\Message\Uri
927
     */
928
    public function addQuery($query)
929
    {
930
        $uri = clone $this;
931
        $query = is_array($query) ? http_build_query($query, PHP_QUERY_RFC3986, '&', PHP_QUERY_RFC3986) : $query;
932
933
        parse_str($query, $newQuery);
934
935
        if ( ! empty($uri->query)) {
936
            parse_str($uri->query, $oldQuery);
937
            $query = array_merge($oldQuery, $newQuery);
938
        } else {
939
            $query = $newQuery;
940
        }
941
942
        if (is_array($query)) {
943
            $uri->query = is_array($query) ? http_build_query($query, PHP_QUERY_RFC3986, '&', PHP_QUERY_RFC3986) : $query;
0 ignored issues
show
introduced by
The condition is_array($query) is always true.
Loading history...
944
        }
945
946
        return $uri;
947
    }
948
949
    // ------------------------------------------------------------------------
950
951
    /**
952
     * Uri::withFragment
953
     *
954
     * Return an instance with the specified URI fragment.
955
     *
956
     * This method MUST retain the state of the current instance, and return
957
     * an instance that contains the specified URI fragment.
958
     *
959
     * Users can provide both encoded and decoded fragment characters.
960
     * Implementations ensure the correct encoding as outlined in getFragment().
961
     *
962
     * An empty fragment value is equivalent to removing the fragment.
963
     *
964
     * @param string $fragment The fragment to use with the new instance.
965
     *
966
     * @return static|\O2System\Kernel\Http\Message\Uri A new instance with the specified fragment.
967
     */
968
    public function withFragment($fragment)
969
    {
970
        $uri = clone $this;
971
        $uri->fragment = $fragment;
972
973
        return $uri;
974
    }
975
976
    // ------------------------------------------------------------------------
977
978
    /**
979
     * Uri::getSuffix
980
     *
981
     * @return string
982
     */
983
    public function getSuffix()
984
    {
985
        return $this->suffix;
986
    }
987
988
    // ------------------------------------------------------------------------
989
990
    /**
991
     * Uri::setSuffix
992
     *
993
     * @param string $suffix
994
     */
995
    protected function setSuffix($suffix)
996
    {
997
        if (is_null($suffix) or is_bool($suffix)) {
0 ignored issues
show
introduced by
The condition is_bool($suffix) is always false.
Loading history...
998
            $this->suffix = null;
999
        } elseif ($suffix === '/') {
1000
            $this->suffix = $suffix;
1001
        } else {
1002
            $this->suffix = '.' . trim($suffix, '.');
1003
        }
1004
    }
1005
1006
    // ------------------------------------------------------------------------
1007
1008
    /**
1009
     * Uri::withSuffix
1010
     *
1011
     * @param string $suffix
1012
     *
1013
     * @return \O2System\Kernel\Http\Message\Uri
1014
     */
1015
    public function withSuffix($suffix)
1016
    {
1017
        $uri = clone $this;
1018
        $uri->setSuffix($suffix);
1019
1020
        return $uri;
1021
    }
1022
1023
    // ------------------------------------------------------------------------
1024
1025
    /**
1026
     * Uri::__toString
1027
     *
1028
     * Return the string representation as a URI reference.
1029
     *
1030
     * Depending on which components of the URI are present, the resulting
1031
     * string is either a full URI or relative reference according to RFC 3986,
1032
     * Section 4.1. The method concatenates the various components of the URI,
1033
     * using the appropriate delimiters:
1034
     *
1035
     * - If a scheme is present, it MUST be suffixed by ":".
1036
     * - If an authority is present, it MUST be prefixed by "//".
1037
     * - The path can be concatenated without delimiters. But there are two
1038
     *   cases where the path has to be adjusted to make the URI reference
1039
     *   valid as PHP does not allow to throw an exception in __toString():
1040
     *     - If the path is rootless and an authority is present, the path MUST
1041
     *       be prefixed by "/".
1042
     *     - If the path is starting with more than one "/" and no authority is
1043
     *       present, the starting slashes MUST be reduced to one.
1044
     * - If a query is present, it MUST be prefixed by "?".
1045
     * - If a fragment is present, it MUST be prefixed by "#".
1046
     *
1047
     * @see http://tools.ietf.org/html/rfc3986#section-4.1
1048
     * @return string
1049
     */
1050
    public function __toString()
1051
    {
1052
        $uriString = $this->scheme . '://';
1053
1054
        if (empty($this->subDomains)) {
1055
            $uriString .= $this->host;
1056
        } else {
1057
            $uriString .= implode('.', $this->subDomains) . '.' . $this->host;
1058
        }
1059
1060
        if ( ! in_array($this->port, [80, 443])) {
1061
            $uriString .= ':' . $this->port;
1062
        }
1063
1064
        $uriPath = empty($this->path)
1065
            ? '/'
1066
            : '/' . trim($this->path, '/') . '/';
1067
1068
        $uriPath .= empty($this->string)
1069
            ? ''
1070
            : '/' . trim($this->string, '/') . '/';
0 ignored issues
show
Bug Best Practice introduced by
The property string does not exist on O2System\Kernel\Http\Message\Uri. Did you maybe forget to declare it?
Loading history...
1071
1072
        $uriPath .= $this->segments->count() == 0
1073
            ? ''
1074
            : '/' . trim($this->segments->__toString(), '/');
1075
1076
        if ($uriPath !== '/' &&
1077
            substr($uriPath, strlen($uriPath) - 1) !== '/' &&
1078
            $this->suffix !== '' && $this->suffix !== '.' &&
1079
            ($uriPath . '/' !== $_SERVER[ 'REQUEST_URI' ]) &&
1080
            pathinfo($uriPath, PATHINFO_EXTENSION) === '' &&
1081
            strpos($uriPath, '#') === false &&
1082
            empty($this->query)
1083
        ) {
1084
            $uriPath .= $this->suffix;
1085
        } elseif (pathinfo($uriPath, PATHINFO_EXTENSION) === '') {
1086
            $uriPath .= '/';
1087
        }
1088
1089
        $uriPath = rtrim($uriPath, '/');
1090
1091
        $uriString .= str_replace('//', '/', $uriPath);
1092
        $uriString .= empty($this->query)
1093
            ? ''
1094
            : '?' . $this->query;
1095
        $uriString .= empty($this->fragment)
1096
            ? ''
1097
            : $this->fragment;
1098
1099
        return $uriString;
1100
    }
1101
}