Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
92.00% covered (success)
92.00%
69 / 75
90.48% covered (success)
90.48%
19 / 21
CRAP
0.00% covered (danger)
0.00%
0 / 1
HttpUri
92.00% covered (success)
92.00%
69 / 75
90.48% covered (success)
90.48%
19 / 21
43.95
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 set
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
1 / 1
6
 getCurrent
n/a
0 / 0
n/a
0 / 0
5
 fromCurrent
n/a
0 / 0
n/a
0 / 0
1
 isValid
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRootPath
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setRootPath
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 setPathOffset
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getSubdomain
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 getPath
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setPath
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getPathOffset
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRoute
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 getQuery
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 setQuery
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getPathElement
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 getPathKey
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 getPathElements
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getQueryArray
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getBase
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __toString
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAuthority
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
3
 getUserInfo
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2/**
3 * Jingga
4 *
5 * PHP Version 8.1
6 *
7 * @package   phpOMS\Uri
8 * @copyright Dennis Eichhorn
9 * @license   OMS License 2.0
10 * @version   1.0.0
11 * @link      https://jingga.app
12 */
13declare(strict_types=1);
14
15namespace phpOMS\Uri;
16
17use phpOMS\Utils\StringUtils;
18
19/**
20 * HTTP Uri.
21 *
22 * Uri used for http requests (incoming & outgoing)
23 *
24 * @package phpOMS\Uri
25 * @license OMS License 2.0
26 * @link    https://jingga.app
27 * @since   1.0.0
28 *
29 * @SuppressWarnings(PHPMD.Superglobals)
30 */
31final class HttpUri implements UriInterface
32{
33    /**
34     * Root path.
35     *
36     * @var string
37     * @since 1.0.0
38     */
39    private string $rootPath = '';
40
41    /**
42     * Path offset.
43     *
44     * @var int
45     * @since 1.0.0
46     */
47    private int $pathOffset = 0;
48
49    /**
50     * Path elements.
51     *
52     * @var string[]
53     * @since 1.0.0
54     */
55    private array $pathElements;
56
57    /**
58     * Uri.
59     *
60     * @var string
61     * @since 1.0.0
62     */
63    public string $uri;
64
65    /**
66     * Uri scheme.
67     *
68     * @var string
69     * @since 1.0.0
70     */
71    public string $scheme;
72
73    /**
74     * Uri host.
75     *
76     * @var string
77     * @since 1.0.0
78     */
79    public string $host;
80
81    /**
82     * Uri port.
83     *
84     * @var int
85     * @since 1.0.0
86     */
87    public int $port;
88
89    /**
90     * Uri user.
91     *
92     * @var string
93     * @since 1.0.0
94     */
95    public string $user;
96
97    /**
98     * Uri password.
99     *
100     * @var string
101     * @since 1.0.0
102     */
103    public string $pass;
104
105    /**
106     * Uri path.
107     *
108     * @var string
109     * @since 1.0.0
110     */
111    public string $path;
112
113    /**
114     * Uri path with offset.
115     *
116     * @var string
117     * @since 1.0.0
118     */
119    private string $offsetPath = '';
120
121    /**
122     * Uri query.
123     *
124     * @var array<string, string>
125     * @since 1.0.0
126     */
127    private array $query = [];
128
129    /**
130     * Uri query.
131     *
132     * @var string
133     * @since 1.0.0
134     */
135    private string $queryString;
136
137    /**
138     * Uri fragment.
139     *
140     * @var string
141     * @since 1.0.0
142     */
143    public string $fragment;
144
145    /**
146     * Uri fragments.
147     *
148     * @var array
149     * @since 1.0.0
150     */
151    public array $fragments = [];
152
153    /**
154     * Uri base.
155     *
156     * @var string
157     * @since 1.0.0
158     */
159    private string $base;
160
161    /**
162     * Constructor.
163     *
164     * @param string $uri Root path for subdirectory
165     *
166     * @since 1.0.0
167     */
168    public function __construct(string $uri)
169    {
170        $this->set($uri);
171    }
172
173    /**
174     * {@inheritdoc}
175     *
176     * @throws \Exception
177     */
178    public function set(string $uri) : void
179    {
180        $this->uri = $uri;
181        $url       = \parse_url($this->uri);
182
183        if ($url === false) {
184            $this->scheme       = '';
185            $this->host         = '';
186            $this->port         = 80;
187            $this->user         = '';
188            $this->pass         = '';
189            $this->path         = '';
190            $this->queryString  = '';
191            $this->query        = [];
192            $this->pathElements = [];
193            $this->fragment     = '';
194            $this->base         = '';
195
196            return;
197        }
198
199        $this->scheme = $url['scheme'] ?? '';
200        $this->host   = $url['host'] ?? '';
201        $this->port   = $url['port'] ?? 80;
202        $this->user   = $url['user'] ?? '';
203        $this->pass   = $url['pass'] ?? '';
204        $this->path   = $url['path'] ?? '';
205
206        if (StringUtils::endsWith($this->path, '.php')) {
207            $path = \substr($this->path, 0, -4);
208
209            if ($path === false) {
210                throw new \Exception(); // @codeCoverageIgnore
211            }
212
213            $this->path = $path;
214        }
215
216        $this->pathElements = \explode('/', \trim($this->path, '/'));
217
218        $path             = \array_slice($this->pathElements, $this->pathOffset);
219        $this->offsetPath = '/' . \implode('/', $path);
220
221        $this->queryString = $url['query'] ?? '';
222
223        if (!empty($this->queryString)) {
224            \parse_str($this->queryString, $this->query);
225        }
226
227        $this->query = \array_change_key_case($this->query, \CASE_LOWER);
228
229        $this->fragment  = $url['fragment'] ?? '';
230        $this->fragments = \explode('&', $url['fragment'] ?? '');
231        $this->base      = $this->scheme . '://' . $this->host . ($this->port !== 80 ? ':' . $this->port : '') . $this->rootPath;
232    }
233
234    /**
235     * Get current uri.
236     *
237     * @return string Returns the current uri
238     *
239     * @since 1.0.0
240     * @codeCoverageIgnore
241     */
242    public static function getCurrent() : string
243    {
244        /** @noinspection PhpUndefinedConstantInspection */
245        return ((!empty($_SERVER['HTTPS'] ?? '') && ($_SERVER['HTTPS'] ?? '') !== 'off')
246            || (($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https')
247            || (($_SERVER['HTTP_X_FORWARDED_SSL'] ?? '') === 'on') ? 'https' : 'http')
248            . '://' . ($_SERVER['HTTP_HOST'] ?? ''). ($_SERVER['REQUEST_URI'] ?? '');
249    }
250
251    /**
252     * Create uri from current url
253     *
254     * @return HttpUri Returns the current uri
255     *
256     * @since 1.0.0
257     * @codeCoverageIgnore
258     */
259    public static function fromCurrent() : self
260    {
261        return new self(self::getCurrent());
262    }
263
264    /**
265     * {@inheritdoc}
266     */
267    public static function isValid(string $uri) : bool
268    {
269        return (bool) \filter_var($uri, \FILTER_VALIDATE_URL);
270    }
271
272    /**
273     * {@inheritdoc}
274     */
275    public function getRootPath() : string
276    {
277        return $this->rootPath;
278    }
279
280    /**
281     * {@inheritdoc}
282     */
283    public function setRootPath(string $root) : void
284    {
285        $this->rootPath = \rtrim($root, '/');
286        $this->base     = $this->scheme . '://' . $this->host . ($this->port !== 80 ? ':' . $this->port : '') . $this->rootPath;
287    }
288
289    /**
290     * {@inheritdoc}
291     */
292    public function setPathOffset(int $offset = 0) : void
293    {
294        $this->pathOffset = $offset;
295
296        $path             = \array_slice($this->pathElements, $this->pathOffset);
297        $this->offsetPath = '/' . \implode('/', $path);
298    }
299
300    /**
301     * Return the subdomain of a host
302     *
303     * @return string
304     *
305     * @since 1.0.0
306     */
307    public function getSubdomain() : string
308    {
309        $host   = \explode('.', $this->host);
310        $length = \count($host) - 2;
311
312        if ($length < 1) {
313            return '';
314        }
315
316        return \implode('.', \array_slice($host, 0, $length));
317    }
318
319    /**
320     * {@inheritdoc}
321     */
322    public function getPath(int $offset = 0) : string
323    {
324        return $this->path;
325    }
326
327    /**
328     * {@inheritdoc}
329     */
330    public function setPath(string $path) : void
331    {
332        $this->path         = $path;
333        $this->pathElements = \explode('/', \ltrim($this->path, '/'));
334
335        $path             = \array_slice($this->pathElements, $this->pathOffset);
336        $this->offsetPath = '/' . \implode('/', $path);
337    }
338
339    /**
340     * Get path offset.
341     *
342     * @return int
343     *
344     * @since 1.0.0
345     */
346    public function getPathOffset() : int
347    {
348        return $this->pathOffset;
349    }
350
351    /**
352     * {@inheritdoc}
353     */
354    public function getRoute(bool $ignoreOffset = false) : string
355    {
356        $path = $ignoreOffset ? $this->path : $this->offsetPath;
357
358        $query = $this->getQuery();
359        return $path . (empty($query) ? '' : '?' . $this->getQuery());
360    }
361
362    /**
363     * {@inheritdoc}
364     */
365    public function getQuery(string $key = null) : string
366    {
367        if ($key !== null) {
368            $key = \strtolower($key);
369
370            return $this->query[$key] ?? '';
371        }
372
373        return $this->queryString;
374    }
375
376    /**
377     * {@inheritdoc}
378     */
379    public function setQuery(string $uri) : void
380    {
381        \parse_str($uri, $this->query);
382
383        $this->query = \array_change_key_case($this->query, \CASE_LOWER);
384    }
385
386    /**
387     * {@inheritdoc}
388     */
389    public function getPathElement(int $pos = 0, bool $useOffset = true) : string
390    {
391        return $this->pathElements[$pos + ($useOffset ? $this->pathOffset : 0)] ?? '';
392    }
393
394    /**
395     * {@inheritdoc}
396     */
397    public function getPathKey(string $key) : string
398    {
399        foreach ($this->pathElements as $index => $element) {
400            if ($element === $key) {
401                return $this->pathElements[$index + 1] ?? '';
402            }
403        }
404
405        return '';
406    }
407
408    /**
409     * {@inheritdoc}
410     */
411    public function getPathElements() : array
412    {
413        return $this->pathElements;
414    }
415
416    /**
417     * {@inheritdoc}
418     */
419    public function getQueryArray() : array
420    {
421        return $this->query;
422    }
423
424    /**
425     * {@inheritdoc}
426     */
427    public function getBase() : string
428    {
429        return $this->base;
430    }
431
432    /**
433     * {@inheritdoc}
434     */
435    public function __toString()
436    {
437        return $this->uri;
438    }
439
440    /**
441     * {@inheritdoc}
442     */
443    public function getAuthority() : string
444    {
445        return ($this->user !== '' ? $this->user . '@' : '') . $this->host
446            . ($this->port !== 0 ? ':' . $this->port : '');
447    }
448
449    /**
450     * {@inheritdoc}
451     */
452    public function getUserInfo() : string
453    {
454        return $this->user . (empty($this->pass) ? '' : ':' . $this->pass);
455    }
456}