Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
92.44% covered (success)
92.44%
110 / 119
97.37% covered (success)
97.37%
37 / 38
CRAP
0.00% covered (danger)
0.00%
0 / 1
Uri
92.44% covered (success)
92.44%
110 / 119
97.37% covered (success)
97.37%
37 / 38
78.50
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
1 / 1
11
 create
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getBasePath
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getScheme
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getHost
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFullHost
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getUsername
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPassword
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPort
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getQuery
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getQueryAsArray
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getFragment
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getUri
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFullUri
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSegment
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSegments
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasBasePath
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasScheme
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasHost
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasUsername
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasPassword
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasUri
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasPort
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasQuery
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasFragment
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasSegments
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasSegment
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setBasePath
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setScheme
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setHost
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setUsername
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setPassword
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setPort
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setQuery
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 setFragment
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setUri
70.97% covered (success)
70.97%
22 / 31
0.00% covered (danger)
0.00%
0 / 1
27.83
 render
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
8
 __toString
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Pop PHP Framework (http://www.popphp.org/)
4 *
5 * @link       https://github.com/popphp/popphp-framework
6 * @author     Nick Sagona, III <dev@nolainteractive.com>
7 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
8 * @license    http://www.popphp.org/license     New BSD License
9 */
10
11/**
12 * @namespace
13 */
14namespace Pop\Http;
15
16/**
17 * HTTP URI class
18 *
19 * @category   Pop
20 * @package    Pop\Http
21 * @author     Nick Sagona, III <dev@nolainteractive.com>
22 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
23 * @license    http://www.popphp.org/license     New BSD License
24 * @version    5.0.0
25 */
26class Uri
27{
28
29    /**
30     * Scheme
31     * @var ?string
32     */
33    protected ?string $scheme = null;
34
35    /**
36     * Host
37     * @var ?string
38     */
39    protected ?string $host = null;
40
41    /**
42     * Username
43     * @var ?string
44     */
45    protected ?string $username = null;
46
47    /**
48     * Password
49     * @var ?string
50     */
51    protected ?string $password = null;
52
53    /**
54     * URI
55     * @var ?string
56     */
57    protected ?string $uri = null;
58
59    /**
60     * Port
61     * @var string|int|null
62     */
63    protected string|int|null $port = null;
64
65    /**
66     * Query
67     * @var ?string
68     */
69    protected ?string $query = null;
70
71    /**
72     * Fragment
73     * @var ?string
74     */
75    protected ?string $fragment = null;
76
77    /**
78     * Base path
79     * @var ?string
80     */
81    protected ?string $basePath = null;
82
83    /**
84     * Path segments
85     * @var array
86     */
87    protected array $segments = [];
88
89    /**
90     * Constructor
91     *
92     * Instantiate the URI object
93     *
94     * @param  ?string $uri
95     * @param  ?string $basePath
96     * @throws Exception
97     */
98    public function __construct(?string $uri = null, ?string $basePath = null)
99    {
100        $path = null;
101        if ($uri !== null) {
102            $uriInfo = parse_url($uri);
103
104            if ($uriInfo === false) {
105                throw new Exception('Error: Unable to parse the URI value.');
106            }
107
108            if (!empty($uriInfo['scheme'])) {
109                $this->setScheme($uriInfo['scheme']);
110            }
111            if (!empty($uriInfo['host'])) {
112                $this->setHost($uriInfo['host']);
113            }
114            if (!empty($uriInfo['user'])) {
115                $this->setUsername($uriInfo['user']);
116            }
117            if (!empty($uriInfo['pass'])) {
118                $this->setPassword($uriInfo['pass']);
119            }
120            if (!empty($uriInfo['port'])) {
121                $this->setPort($uriInfo['port']);
122            }
123            if (!empty($uriInfo['query'])) {
124                $this->setQuery($uriInfo['query']);
125            }
126            if (!empty($uriInfo['fragment'])) {
127                $this->setFragment($uriInfo['fragment']);
128            }
129            if (!empty($uriInfo['path'])) {
130                $path = $uriInfo['path'];
131            }
132        }
133
134        $this->setUri($path, $basePath);
135    }
136
137    /**
138     * Create URI object
139     *
140     * @param  ?string $uri
141     * @param  ?string $basePath
142     * @throws Exception
143     * @return Uri
144     */
145    public static function create(?string $uri = null, ?string $basePath = null): Uri
146    {
147        return new self($uri, $basePath);
148    }
149
150    /**
151     * Get the base path
152     *
153     * @return string
154     */
155    public function getBasePath(): string
156    {
157        return $this->basePath;
158    }
159
160    /**
161     * Get the scheme
162     *
163     * @return string
164     */
165    public function getScheme(): string
166    {
167        return $this->scheme;
168    }
169
170    /**
171     * Get the host
172     *
173     * @return string
174     */
175    public function getHost(): string
176    {
177        return $this->host;
178    }
179
180    /**
181     * Get the host with the port
182     *
183     * @return string
184     */
185    public function getFullHost(): string
186    {
187        $host = $this->host;
188        if ($this->hasPort()) {
189            $host .= ':' . $this->port;
190        }
191
192        return $host;
193    }
194
195    /**
196     * Get the username
197     *
198     * @return string
199     */
200    public function getUsername(): string
201    {
202        return $this->username;
203    }
204
205    /**
206     * Get the password
207     *
208     * @return string
209     */
210    public function getPassword(): string
211    {
212        return $this->password;
213    }
214
215    /**
216     * Get the port
217     *
218     * @return string|int|null
219     */
220    public function getPort(): string|int|null
221    {
222        return $this->port;
223    }
224
225    /**
226     * Get the query
227     *
228     * @return string
229     */
230    public function getQuery(): string
231    {
232        return $this->query;
233    }
234
235    /**
236     * Get the query
237     *
238     * @return array
239     */
240    public function getQueryAsArray(): array
241    {
242        $result = [];
243
244        if ($this->query !== null) {
245            parse_str($this->query, $result);
246        }
247
248        return $result;
249    }
250
251    /**
252     * Get the fragment
253     *
254     * @return string
255     */
256    public function getFragment(): string
257    {
258        return $this->fragment;
259    }
260
261    /**
262     * Get the URI
263     *
264     * @return string
265     */
266    public function getUri(): string
267    {
268        return $this->uri;
269    }
270
271    /**
272     * Get the full URI, including base path
273     *
274     * @return string
275     */
276    public function getFullUri(): string
277    {
278        return $this->basePath . $this->uri;
279    }
280
281    /**
282     * Get a path segment, divided by the forward slash,
283     * where $i refers to the array key index, i.e.,
284     *    0     1     2
285     * /hello/world/page
286     *
287     * @param  int $i
288     * @return string|null
289     */
290    public function getSegment(int $i): string|null
291    {
292        return $this->segments[(int)$i] ?? null;
293    }
294
295    /**
296     * Get all path segments
297     *
298     * @return array
299     */
300    public function getSegments(): array
301    {
302        return $this->segments;
303    }
304
305    /**
306     * Has a base path
307     *
308     * @return bool
309     */
310    public function hasBasePath(): bool
311    {
312        return ($this->basePath !== null);
313    }
314
315    /**
316     * Has a scheme
317     *
318     * @return bool
319     */
320    public function hasScheme(): bool
321    {
322        return ($this->scheme !== null);
323    }
324
325    /**
326     * Has a host
327     *
328     * @return bool
329     */
330    public function hasHost(): bool
331    {
332        return ($this->host !== null);
333    }
334
335    /**
336     * Has a username
337     *
338     * @return bool
339     */
340    public function hasUsername(): bool
341    {
342        return ($this->query !== null);
343    }
344
345    /**
346     * Has a password
347     *
348     * @return bool
349     */
350    public function hasPassword(): bool
351    {
352        return ($this->fragment !== null);
353    }
354
355    /**
356     * Has a uri
357     *
358     * @return bool
359     */
360    public function hasUri(): bool
361    {
362        return ($this->uri !== null);
363    }
364
365    /**
366     * Has a port
367     *
368     * @return bool
369     */
370    public function hasPort(): bool
371    {
372        return ($this->port !== null);
373    }
374
375    /**
376     * Has a query
377     *
378     * @return bool
379     */
380    public function hasQuery(): bool
381    {
382        return ($this->query !== null);
383    }
384
385    /**
386     * Has a fragment
387     *
388     * @return bool
389     */
390    public function hasFragment(): bool
391    {
392        return ($this->fragment !== null);
393    }
394
395    /**
396     * Has segments
397     *
398     * @return bool
399     */
400    public function hasSegments(): bool
401    {
402        return !empty($this->segments);
403    }
404
405    /**
406     * Has segment
407     *
408     * @return bool
409     */
410    public function hasSegment($i): bool
411    {
412        return isset($this->segments[$i]);
413    }
414
415    /**
416     * Set the base path
417     *
418     * @param  ?string $path
419     * @return Uri
420     */
421    public function setBasePath(?string $path = null): Uri
422    {
423        $this->basePath = $path;
424        return $this;
425    }
426
427    /**
428     * Set the scheme
429     *
430     * @param  string $scheme
431     * @return Uri
432     */
433    public function setScheme(string $scheme): Uri
434    {
435        $this->scheme = $scheme;
436        return $this;
437    }
438
439    /**
440     * Set the host
441     *
442     * @param  string $host
443     * @return Uri
444     */
445    public function setHost(string $host): Uri
446    {
447        $this->host = $host;
448        return $this;
449    }
450
451    /**
452     * Set the username
453     *
454     * @param  string $username
455     * @return Uri
456     */
457    public function setUsername(string $username): Uri
458    {
459        $this->username = $username;
460        return $this;
461    }
462
463    /**
464     * Set the password
465     *
466     * @param  string $password
467     * @return Uri
468     */
469    public function setPassword(string $password): Uri
470    {
471        $this->password = $password;
472        return $this;
473    }
474
475    /**
476     * Set the port
477     *
478     * @param  string|int $port
479     * @return Uri
480     */
481    public function setPort(string|int $port): Uri
482    {
483        $this->port = $port;
484        return $this;
485    }
486
487    /**
488     * Set the query
489     *
490     * @param  string|array $query
491     * @return Uri
492     */
493    public function setQuery(string|array $query): Uri
494    {
495        if (is_array($query)) {
496            $query = http_build_query($query);
497        }
498        $this->query = $query;
499        return $this;
500    }
501
502    /**
503     * Set the fragment
504     *
505     * @param  string $fragment
506     * @return Uri
507     */
508    public function setFragment(string $fragment): Uri
509    {
510        $this->fragment = $fragment;
511        return $this;
512    }
513
514    /**
515     * Set the URI
516     *
517     * @param  ?string $uri
518     * @param  ?string $basePath
519     * @return Uri
520     */
521    public function setUri(?string $uri = null, ?string $basePath = null): Uri
522    {
523        $isServerRequest = false;
524        if (($uri === null) && isset($_SERVER['REQUEST_URI'])) {
525            $uri = $_SERVER['REQUEST_URI'];
526            $isServerRequest = true;
527        }
528
529        if (!empty($basePath)) {
530            if (substr($uri, 0, (strlen($basePath) + 1)) == $basePath . '/') {
531                $uri = substr($uri, (strpos($uri, $basePath) + strlen($basePath)));
532            } else if (substr($uri, 0, (strlen($basePath) + 1)) == $basePath . '?') {
533                $uri = '/' . substr($uri, (strpos($uri, $basePath) + strlen($basePath)));
534            }
535        }
536
537        if (($uri == '') || ($uri == $basePath)) {
538            $uri = '/';
539        }
540
541        // Some slash clean up
542        $this->uri = $uri;
543
544        if ($isServerRequest) {
545            $docRoot = (isset($_SERVER['DOCUMENT_ROOT'])) ? str_replace('\\', '/', $_SERVER['DOCUMENT_ROOT']) : null;
546            $dir     = str_replace('\\', '/', getcwd());
547
548            if (($dir != $docRoot) && (strlen($dir) > strlen($docRoot))) {
549                $realBasePath = str_replace($docRoot, '', $dir);
550                if (str_starts_with($uri, $realBasePath)) {
551                    $this->uri = substr($uri, strlen($realBasePath));
552                }
553            }
554
555            $this->setBasePath((($basePath === null) ? str_replace($docRoot, '', $dir) : $basePath));
556        } else {
557            $this->setBasePath($basePath);
558        }
559
560        // Get fragment
561        if (str_contains($this->uri, '#')) {
562            $this->fragment = substr($this->uri, (strpos($this->uri, '#') + 1));
563            $this->uri      = substr($this->uri, 0, strpos($this->uri, '#'));
564        }
565
566        // Get query
567        if (str_contains($this->uri, '?')) {
568            $this->query = substr($this->uri, (strpos($this->uri, '?') + 1));
569            $this->uri   = substr($this->uri, 0, strpos($this->uri, '?'));
570        }
571
572        // Get segments
573        if (($this->uri != '/') && (str_contains($this->uri, '/'))) {
574            $uri = (str_starts_with($this->uri, '/')) ? substr($this->uri, 1) : $this->uri;
575            $this->segments = explode('/', $uri);
576        }
577
578        return $this;
579    }
580
581    /**
582     * Render the URI
583     *
584     * @return string
585     */
586    public function render(): string
587    {
588        $uri = '';
589
590        if ($this->hasScheme()) {
591            $uri .= $this->getScheme() . '://';
592        }
593        if (($this->hasUsername()) && ($this->hasPassword())) {
594            $uri .= $this->getUsername() . ':' . $this->getPassword() . '@';
595        }
596        if ($this->hasHost()) {
597            $uri .= $this->getHost();
598        }
599        if ($this->hasPort()) {
600            $uri .= ':' . $this->getPort();
601        }
602
603        $uri .= $this->getFullUri();
604
605        if ($this->hasQuery()) {
606            $uri .= '?' . $this->getQuery();
607        }
608        if ($this->hasFragment()) {
609            $uri .= '#' . $this->getFragment();
610        }
611
612        return $uri;
613    }
614
615    /**
616     * Render the URI
617     *
618     * @return string
619     */
620    public function __toString(): string
621    {
622        return $this->render();
623    }
624
625}