Passed
Push — master ( fca16e...a09e23 )
by Alexander
04:10
created

URI::applyParts()   B

Complexity

Conditions 9
Paths 192

Size

Total Lines 32
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 17
nc 192
nop 1
dl 0
loc 32
rs 7.2888
c 1
b 0
f 0
1
<?php    
2
3
/**
4
 * Lenevor Framework
5
 *
6
 * LICENSE
7
 *
8
 * This source file is subject to the new BSD license that is bundled
9
 * with this package in the file license.md.
10
 * It is also available through the world-wide-web at this URL:
11
 * https://lenevor.com/license
12
 * If you did not receive a copy of the license and are unable to
13
 * obtain it through the world-wide-web, please send an email
14
 * to [email protected] so we can send you a copy immediately.
15
 *
16
 * @package     Lenevor
17
 * @subpackage  Base
18
 * @link        https://lenevor.com
19
 * @copyright   Copyright (c) 2019 - 2023 Alexander Campo <[email protected]>
20
 * @license     https://opensource.org/licenses/BSD-3-Clause New BSD license or see https://lenevor.com/license or see /license.md
21
 */
22
23
namespace Syscodes\Components\Http;
24
25
use Syscodes\Components\Support\Arr;
26
use Syscodes\Components\Support\Facades\Request;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Syscodes\Components\Http\Request. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
27
use Syscodes\Components\Http\Exceptions\HttpURIException;
28
29
/**
30
 * Abstraction for a uniform resource identifier (URI).
31
 */
32
class URI
33
{
34
	/**
35
	 * Returns default schemes/ports.
36
	 * 
37
	 * @var array $defaultPorts
38
	 */
39
	protected $defaultPorts = [
40
		'http'  => 80,
41
		'https' => 443,
42
		'ftp'   => 21,
43
		'sftp'  => 22
44
	];
45
46
	/**
47
	 * The name of any fragment.
48
	 * 
49
	 * @var string $fragment
50
	 */
51
	protected $fragment = '';
52
53
	/**
54
	 * The URI Host.
55
	 * 
56
	 * @var string $host
57
	 */
58
	protected $host;
59
	
60
	/**
61
	 * The URI User Password.
62
	 * 
63
	 * @var string $password
64
	 */
65
	protected $password;
66
67
	/**
68
	 * The URI path.
69
	 * 
70
	 * @var string $path
71
	 */
72
	protected $path;
73
74
	/**
75
	 * The URI Port.
76
	 * 
77
	 * @var int $port
78
	 */
79
	protected $port;
80
81
	/**
82
	 * The query string.
83
	 * 
84
	 * @var string $query
85
	 */
86
	protected $query;
87
88
	/**
89
	 * The URI Scheme.
90
	 * 
91
	 * @var string $scheme
92
	 */
93
	protected $scheme = 'http';
94
95
	/**
96
	 * Whether passwords should be shown in userInfo/authority calls.
97
	 * 
98
	 * @var boolean $showPassword
99
	 */
100
	protected $showPassword = false;
101
	
102
	/**
103
	 * The URI User Info.
104
	 * 
105
	 * @var string $user
106
	 */
107
	protected $user;
108
109
	/**
110
	 * Constructor. The URI class instance.
111
	 * 
112
	 * @param  string|null  $uri  
113
	 * 
114
	 * @return void
115
	 * 
116
	 * @throws \Syscodes\Components\Http\Exceptions\HttpURIException
117
	 */
118
	public function __construct(string $uri = null)
119
	{
120
		if ( ! is_null($uri)) {
121
			$this->setUri($uri);
122
		}
123
	}
124
125
	/**
126
	 * Sets and overwrites any current URI information.
127
	 * 
128
	 * @param  string|null  $uri  
129
	 * 
130
	 * @return static
131
	 * 
132
	 * @throws \Syscodes\Components\Http\Exceptions\HttpURIException
133
	 */
134
	public function setUri(string $uri = null): static
135
	{
136
		if ( ! is_null($uri)) {
137
			$parts = parse_url($uri);
138
139
			if ($parts === false) {
140
				throw HttpURIException::UnableToParseURI($uri);
141
			}
142
143
			$this->applyParts($parts);
144
		}
145
146
		return $this;
147
	}
148
149
	/**
150
	 * Returns the full URI string.
151
	 *
152
	 * @return string  The URI string
153
	 */
154
	public function get(): string
155
	{
156
		return '/'.ltrim($this->path, '/');
157
	}
158
159
	/**
160
	 * Sets of URI string.
161
	 * 
162
	 * @param  string  $uri
163
	 * 
164
	 * @return static
165
	 */
166
	public function set(string $uri): static
167
	{
168
		$this->path = $uri;
169
170
		return $this;
171
	}
172
173
	/**
174
	 * Retrieve the path component of the URI. The path can either be empty or absolute 
175
	 * (starting with a slash) or rootless (not starting with a slash).
176
	 * 
177
	 * @return string
178
	 */
179
	public function getPath(): string
180
	{
181
		return (is_null($this->path) ? '' : $this->path);
0 ignored issues
show
introduced by
The condition is_null($this->path) is always false.
Loading history...
182
	}
183
184
	/**
185
	 * Sets the path portion of the URI.
186
	 * 
187
	 * @param  string  $uri
188
	 *
189
	 * @return array
190
	 */
191
	public function setPath(string $uri): array
192
	{
193
		$this->path = $this->filterPath($uri);
194
195
		$tempPath = trim($this->path, '/');
196
197
		return $this->filterSegments($tempPath);
198
	} 
199
200
	/**
201
	 * Encodes any dangerous characters.
202
	 * 
203
	 * @param  string  $uri
204
	 * 
205
	 * @return string
206
	 */
207
	protected function filterPath(string $uri): string
208
	{
209
		return urldecode($uri);
210
	}
211
212
	/**
213
	 * Filter the segments of path.
214
	 * 
215
	 * @param  string  $uri
216
	 * 
217
	 * @return string[]
218
	 */
219
	protected function filterSegments(string $uri): array
220
	{
221
		return ($uri == '') ? [] : explode('/', $uri);
222
	}
223
224
	/**
225
	 * Get the specified URI segment, return default if it doesn't exist.
226
	 * Segment index is 1 based, not 0 based.
227
	 *
228
	 * @param  int  $index  The 1-based segment index
229
	 * @param  mixed  $default  The default value
230
	 *
231
	 * @return mixed
232
	 */
233
	public function getSegment(int $index, $default = null): mixed
234
	{
235
		return Arr::get($this->getSegments(), $index - 1, $default);
236
	}
237
238
	/**
239
	 * Returns the segments of the path as an array.
240
	 *
241
	 * @return array  The URI segments
242
	 */
243
	public function getSegments(): array
244
	{
245
		$segments = $this->setPath(Request::decodedPath());
0 ignored issues
show
Bug introduced by
The method decodedPath() does not exist on Syscodes\Components\Support\Facades\Request. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

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

245
		$segments = $this->setPath(Request::/** @scrutinizer ignore-call */ decodedPath());
Loading history...
246
247
		return array_values(array_filter($segments, fn ($value) => $value != ''));
248
	}
249
250
	/**
251
	 * Returns the total number of segment.
252
	 *
253
	 * @return int  
254
	 */
255
	public function getTotalSegments(): int
256
	{
257
		return count($this->getSegments());
258
	}
259
260
	/**
261
	 * Retrieve the scheme component of the URI.
262
	 * 
263
	 * @return string
264
	 */
265
	public function getScheme(): string
266
	{
267
		return $this->scheme;
268
	}
269
270
	/**
271
	 * Sets the scheme for this URI.
272
	 * 
273
	 * @param  string  $str
274
	 * 
275
	 * @return string
276
	 */
277
	public function setScheme(string $str): string
278
	{
279
		$str = preg_replace('~:(//)?$~', '', strtolower($str));
280
281
		$this->scheme = $str;
282
283
		return $this->scheme;
284
	}
285
286
	/**
287
	 * Retrieve the user component of the URI.
288
	 * 
289
	 * @return string|null
290
	 */
291
	public function getUserInfo()
292
	{
293
		$user = $this->user;
294
		$pass = $this->password;
295
296
		if ($this->showPassword === true && ! empty($pass)) {
297
			$user .= ":$pass";
298
		}
299
300
		return $user;
301
	}
302
303
	/**
304
	 * Sets the user portion of the URI.
305
	 * 
306
	 * @param  string  $user
307
	 * 
308
	 * @return string|null
309
	 */
310
	public function setUser($user): string
311
	{
312
		$this->user = trim($user);
313
314
		return $this->user;
315
	}
316
317
	/**
318
	 * Sets the password portion of the URI.
319
	 * 
320
	 * @param  string  $password
321
	 * 
322
	 * @return string|null
323
	 */
324
	public function setPassword($password): string
325
	{
326
		$this->password = trim($password);
327
328
		return $this->password;
329
	}
330
331
	/**
332
	 * Temporarily sets the URI to show a password in userInfo.
333
	 * 
334
	 * @param  boolean  $option  
335
	 * 
336
	 * @return static
337
	 */
338
	public function showPassword(bool $option = true): static
339
	{
340
		$this->showPassword = $option;
341
342
		return $this;
343
	}
344
345
	/**
346
	 * Retrieve the authority component of the URI.
347
	 * 
348
	 * @param  boolean  $ignore  
349
	 * 
350
	 * @return string
351
	 */
352
	public function getAuthority(bool $ignore = false): string
353
	{
354
		if (empty($this->host)) {
355
			return '';
356
		}
357
358
		$authority = $this->host;
359
360
		if ( ! empty($this->getUserInfo())) {
361
			$authority = $this->getUserInfo().'@'.$authority;
362
		}
363
364
		if ( ! empty($this->port) && ! $ignore) {
365
			if ($this->port !== $this->defaultPorts[$this->scheme]) {
366
				$authority .= ":$this->port";
367
			}
368
		}
369
370
		$this->showPassword = false;
371
372
		return $authority;
373
	}
374
375
	/**
376
	 * Parses the given string an saves the appropriate authority pieces.
377
	 * 
378
	 * @param  string  $str
379
	 * 
380
	 * @return static
381
	 */
382
	public function setAuthority(string $str): static
383
	{
384
		$parts = parse_url($str);
385
386
		if (empty($parts['host']) && ! empty($parts['path'])) {
387
			$parts['host'] = $parts['path'];
388
			unset($parts['path']);
389
		}
390
391
		$this->applyParts($parts);
392
393
		return $this;
394
	}
395
396
	/**
397
	 * Retrieve the host component of the URI.
398
	 * 
399
	 * @return string
400
	 */
401
	public function getHost(): string
402
	{
403
		return $this->host;
404
	}
405
406
	/**
407
	 * Sets the host name to use.
408
	 * 
409
	 * @param  string  $str
410
	 * 
411
	 * @return string
412
	 */
413
	public function setHost(string $str): string
414
	{
415
		$this->host = trim($str);
416
417
		return $this->host;
418
	}
419
420
	/**
421
	 * Retrieve the port component of the URI.
422
	 * 
423
	 * @return int|null
424
	 */
425
	public function getPort()
426
	{
427
		return $this->port;
428
	}
429
430
	/**
431
	 * Sets the port portion of the URI.
432
	 * 
433
	 * @param  int|null  $port  
434
	 * 
435
	 * @return string
436
	 */
437
	public function setPort(int $port = null): string
438
	{
439
		if (is_null($port)) {
440
			return $this;
441
		}
442
443
		if ($port <= 0 || $port > 65355) {
444
			throw HttpURIException::invalidPort($port);
445
		}
446
447
		$this->port = $port;
448
449
		return $this->port;
450
	}
451
452
	/**
453
	 * Retrieve a URI fragment.
454
	 * 
455
	 * @return string
456
	 */
457
	public function getFragment(): string
458
	{
459
		return is_null($this->fragment) ? '' : $this->fragment;
0 ignored issues
show
introduced by
The condition is_null($this->fragment) is always false.
Loading history...
460
	}
461
462
	/**
463
	 * Sets the fragment portion of the URI.
464
	 * 
465
	 * @param  string  $str
466
	 * 
467
	 * @return string
468
	 */
469
	public function setFragment(string $str): string
470
	{
471
		$this->fragment = trim($str, '# ');
472
473
		return $this->fragment;
474
	}
475
476
	/**
477
	 * Saves our parts from a parse_url call.
478
	 * 
479
	 * @param  array  $parts
480
	 * 
481
	 * @return mixed
482
	 */
483
	public function applyParts(array $paths)
0 ignored issues
show
Unused Code introduced by
The parameter $paths is not used and could be removed. ( Ignorable by Annotation )

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

483
	public function applyParts(/** @scrutinizer ignore-unused */ array $paths)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
484
	{
485
		if (isset($parts['scheme'])) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $parts seems to never exist and therefore isset should always be false.
Loading history...
486
			$this->SetScheme(rtrim($parts['scheme'], ':/'));
487
		} else {
488
			$this->setScheme('http');
489
		}
490
491
		if ( ! empty($parts['host'])) {
492
			$this->host = $parts['host'];
493
		}
494
495
		if (isset($parts['port'])) {
496
			if ( ! is_null($parts['port'])) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $parts seems to be never defined.
Loading history...
497
				$this->port = $parts['port'];
498
			}
499
		}
500
501
		if ( ! empty($parts['user'])) {
502
			$this->user = $parts['user'];
503
		}
504
505
		if ( ! empty($parts['pass'])) {
506
			$this->password = $parts['pass'];
507
		}
508
509
		if ( ! empty($parts['path'])) {
510
			$this->path = $this->filterPath($parts['path']);
511
		}
512
513
		if ( ! empty($parts['fragment'])) {
514
			$this->fragment = $parts['fragment'];
515
		}
516
	}
517
518
	/**
519
	 * Magic method.
520
	 * 
521
	 * Returns the URI string.
522
	 *
523
	 * @return string
524
	 */
525
	public function __toString(): string
526
	{
527
		return (string) $this->getPath();
528
	}
529
}