Passed
Push — master ( 569c77...b450b1 )
by Tomáš
04:03
created

Url::isEqual()   B

Complexity

Conditions 10
Paths 18

Size

Total Lines 16
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 10

Importance

Changes 0
Metric Value
cc 10
eloc 14
nc 18
nop 1
dl 0
loc 16
ccs 15
cts 15
cp 1
crap 10
rs 7.6666
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php declare(strict_types = 1);
2
3
namespace Apicart\Utils\Http;
4
5
use InvalidArgumentException;
6
use JsonSerializable;
7
8
/**
9
 * @author David Grudl (https://nette.org/)
10
 *
11
 * URI Syntax (RFC 3986)
12
 *
13
 * scheme  user  password   host   port basePath   relativeUrl
14
 *   |      |      |         |       |    |             |
15
 * /--\   /--\ /------\ /---------\ /--\/--\/----------------------------\
16
 * http://user:[email protected]:1234/en/index.php?name=value#fragment  <-- absoluteUrl
17
 *        \____________________________/\____________/^\________/^\______/
18
 *                     |                       |           |         |
19
 *                 authority                  path       query    fragment
20
 *
21
 * - authority:   [user[:password]@]host[:port]
22
 * - hostUrl:     http://user:[email protected]:8042
23
 * - basePath:    /en/ (everything before relative URI not including the script name)
24
 * - baseUrl:     http://user:[email protected]:8042/en/
25
 */
26
final class Url implements JsonSerializable
27
{
28
29
	/**
30
	 * @var array
31
	 */
32
	public static $defaultPorts = [
33
		'http' => 80,
34
		'https' => 443,
35
		'ftp' => 21,
36
		'news' => 119,
37
		'nntp' => 119,
38
	];
39
40
	/**
41
	 * @var string
42
	 */
43
	private $scheme = '';
44
45
	/**
46
	 * @var string
47
	 */
48
	private $user = '';
49
50
	/**
51
	 * @var string
52
	 */
53
	private $password = '';
54
55
	/**
56
	 * @var string
57
	 */
58
	private $host = '';
59
60
	/**
61
	 * @var int|null
62
	 */
63
	private $port;
64
65
	/**
66
	 * @var string
67
	 */
68
	private $path = '';
69
70
	/**
71
	 * @var array
72
	 */
73
	private $query = [];
74
75
	/**
76
	 * @var string
77
	 */
78
	private $fragment = '';
79
80
81
	/**
82
	 * @param  string $url
83
	 * @throws InvalidArgumentException if URL is malformed
84
	 */
85 19
	public function __construct($url = null)
86
	{
87 19
		if (is_string($url)) {
88 19
			$p = @parse_url($url); // @ - is escalated to exception
89 19
			if ($p === false) {
90
				throw new InvalidArgumentException("Malformed or unsupported URI '${url}'.");
91
			}
92
93 19
			$this->scheme = $p['scheme'] ?? '';
94 19
			$this->port = $p['port'] ?? null;
95 19
			$this->host = isset($p['host']) ? rawurldecode($p['host']) : '';
96 19
			$this->user = isset($p['user']) ? rawurldecode($p['user']) : '';
97 19
			$this->password = isset($p['pass']) ? rawurldecode($p['pass']) : '';
98 19
			$this->setPath($p['path'] ?? '');
99 19
			$this->setQuery($p['query'] ?? []);
100 19
			$this->fragment = isset($p['fragment']) ? rawurldecode($p['fragment']) : '';
101
		}
102 19
	}
103
104
105
	/**
106
	 * @return string
107
	 */
108 1
	public function __toString()
109
	{
110 1
		return $this->getAbsoluteUrl();
111
	}
112
113
114
	/**
115
	 * Sets the scheme part of URI.
116
	 * @param  string $value
117
	 * @return static
118
	 */
119
	public function setScheme($value)
120
	{
121
		$this->scheme = (string) $value;
122
		return $this;
123
	}
124
125
126
	/**
127
	 * Returns the scheme part of URI.
128
	 * @return string
129
	 */
130 1
	public function getScheme()
131
	{
132 1
		return $this->scheme;
133
	}
134
135
136
	/**
137
	 * Sets the user name part of URI.
138
	 * @param  string $value
139
	 * @return static
140
	 */
141
	public function setUser($value)
142
	{
143
		$this->user = (string) $value;
144
		return $this;
145
	}
146
147
148
	/**
149
	 * Returns the user name part of URI.
150
	 * @return string
151
	 */
152 1
	public function getUser()
153
	{
154 1
		return $this->user;
155
	}
156
157
158
	/**
159
	 * Sets the password part of URI.
160
	 * @param  string $value
161
	 * @return static
162
	 */
163
	public function setPassword($value)
164
	{
165
		$this->password = (string) $value;
166
		return $this;
167
	}
168
169
170
	/**
171
	 * Returns the password part of URI.
172
	 * @return string
173
	 */
174 1
	public function getPassword()
175
	{
176 1
		return $this->password;
177
	}
178
179
180
	/**
181
	 * Sets the host part of URI.
182
	 * @param  string $value
183
	 * @return static
184
	 */
185
	public function setHost($value)
186
	{
187
		$this->host = (string) $value;
188
		$this->setPath($this->path);
189
		return $this;
190
	}
191
192
193
	/**
194
	 * Returns the host part of URI.
195
	 * @return string
196
	 */
197 1
	public function getHost()
198
	{
199 1
		return $this->host;
200
	}
201
202
203
	/**
204
	 * Returns the part of domain.
205
	 * @return string
206
	 */
207 1
	public function getDomain(int $level = 2)
208
	{
209 1
		$parts = ip2long($this->host) !== false ? [$this->host] : explode('.', $this->host);
210 1
		$parts = $level >= 0 ? array_slice($parts, -$level) : array_slice($parts, 0, $level);
211 1
		return implode('.', $parts);
212
	}
213
214
215
	/**
216
	 * Sets the port part of URI.
217
	 * @param  int $value
218
	 * @return static
219
	 */
220
	public function setPort(int $value)
221
	{
222
		$this->port = $value;
223
		return $this;
224
	}
225
226
227
	/**
228
	 * Returns the port part of URI.
229
	 * @return int|null
230
	 */
231 2
	public function getPort()
232
	{
233 2
		return $this->port ?: (isset(self::$defaultPorts[$this->scheme]) ? self::$defaultPorts[$this->scheme] : null);
234
	}
235
236
237
	/**
238
	 * Sets the path part of URI.
239
	 * @param  string $value
240
	 * @return static
241
	 */
242 19
	public function setPath($value)
243
	{
244 19
		$this->path = (string) $value;
245 19
		if ($this->host && substr($this->path, 0, 1) !== '/') {
246 1
			$this->path = '/' . $this->path;
247
		}
248 19
		return $this;
249
	}
250
251
252
	/**
253
	 * Returns the path part of URI.
254
	 * @return string
255
	 */
256 1
	public function getPath()
257
	{
258 1
		return $this->path;
259
	}
260
261
262
	/**
263
	 * Sets the query part of URI.
264
	 * @param  string $value|array
265
	 * @return static
266
	 */
267 19
	public function setQuery($value)
268
	{
269 19
		$this->query = is_array($value) ? $value : self::parseQuery($value);
270 19
		return $this;
271
	}
272
273
274
	/**
275
	 * Appends the query part of URI.
276
	 * @param  string $value|array
277
	 * @return static
278
	 */
279
	public function appendQuery($value)
280
	{
281
		$this->query = is_array($value)
282
			? $value + $this->query
283
			: self::parseQuery($this->getQuery() . '&' . $value);
284
		return $this;
285
	}
286
287
288
	/**
289
	 * Returns the query part of URI.
290
	 * @return string
291
	 */
292 5
	public function getQuery()
293
	{
294 5
		return http_build_query($this->query, '', '&', PHP_QUERY_RFC3986);
295
	}
296
297
298
	/**
299
	 * @return array
300
	 */
301
	public function getQueryParameters()
302
	{
303
		return $this->query;
304
	}
305
306
307
	/**
308
	 * @param string $name
309
	 * @param mixed $default
310
	 * @return mixed
311
	 */
312 1
	public function getQueryParameter($name, $default = null)
313
	{
314 1
		return isset($this->query[$name]) ? $this->query[$name] : $default;
315
	}
316
317
318
	/**
319
	 * @param string $name
320
	 * @param mixed $value null unsets the parameter
321
	 * @return static
322
	 */
323
	public function setQueryParameter($name, $value)
324
	{
325
		$this->query[$name] = $value;
326
		return $this;
327
	}
328
329
330
	/**
331
	 * Sets the fragment part of URI.
332
	 * @param  string $value
333
	 * @return static
334
	 */
335
	public function setFragment($value)
336
	{
337
		$this->fragment = (string) $value;
338
		return $this;
339
	}
340
341
342
	/**
343
	 * Returns the fragment part of URI.
344
	 * @return string
345
	 */
346 1
	public function getFragment()
347
	{
348 1
		return $this->fragment;
349
	}
350
351
352
	/**
353
	 * Returns the entire URI including query string and fragment.
354
	 * @return string
355
	 */
356 4
	public function getAbsoluteUrl()
357
	{
358 4
		$query = $this->getQuery();
359
360 4
		return $this->getHostUrl() . $this->path
361 4
			. ($query !== '' ? '?' . $query : '')
362 4
			. ($this->fragment === '' ? '' : '#' . $this->fragment);
363
	}
364
365
366
	/**
367
	 * Returns the [user[:pass]@]host[:port] part of URI.
368
	 * @return string
369
	 */
370 7
	public function getAuthority()
371
	{
372 7
		return $this->host === ''
373
			? ''
374 7
			: ($this->user !== '' && $this->scheme !== 'http' && $this->scheme !== 'https'
375
				? rawurlencode($this->user) . ($this->password === '' ? '' : ':' . rawurlencode($this->password)) . '@'
376 7
				: '')
377 7
			. $this->host
378 7
			. ($this->port !== null
379 7
				&& (! isset(self::$defaultPorts[$this->scheme]) || $this->port !== self::$defaultPorts[$this->scheme])
380 7
				? ':' . $this->port
381 7
				: '');
382
	}
383
384
385
	/**
386
	 * Returns the scheme and authority part of URI.
387
	 * @return string
388
	 */
389 6
	public function getHostUrl()
390
	{
391 6
		return ($this->scheme ? $this->scheme . ':' : '')
392 6
			. (($authority = $this->getAuthority()) || $this->scheme ? '//' . $authority : '');
393
	}
394
395
396
	/**
397
	 * Returns the base-path.
398
	 * @return string
399
	 */
400 3
	public function getBasePath()
401
	{
402 3
		$pos = strrpos($this->path, '/');
403 3
		return $pos === false ? '' : substr($this->path, 0, $pos + 1);
404
	}
405
406
407
	/**
408
	 * Returns the base-URI.
409
	 * @return string
410
	 */
411 2
	public function getBaseUrl()
412
	{
413 2
		return $this->getHostUrl() . $this->getBasePath();
414
	}
415
416
417
	/**
418
	 * Returns the relative-URI.
419
	 * @return string
420
	 */
421 1
	public function getRelativeUrl()
422
	{
423 1
		return (string) substr($this->getAbsoluteUrl(), strlen($this->getBaseUrl()));
424
	}
425
426
427
	/**
428
	 * URL comparison.
429
	 * @param  string $url|self
430
	 * @return bool
431
	 */
432 1
	public function isEqual($url)
433
	{
434 1
		$url = new self($url);
435 1
		$query = $url->query;
436 1
		ksort($query);
437 1
		$query2 = $this->query;
438 1
		ksort($query2);
439 1
		$http = in_array($this->scheme, ['http', 'https'], true);
440 1
		return $url->scheme === $this->scheme
441 1
			&& strcasecmp($url->host, $this->host) === 0
442 1
			&& $url->getPort() === $this->getPort()
443 1
			&& ($http || $url->user === $this->user)
444 1
			&& ($http || $url->password === $this->password)
445 1
			&& self::unescape($url->path, '%/') === self::unescape($this->path, '%/')
446 1
			&& $query === $query2
447 1
			&& $url->fragment === $this->fragment;
448
	}
449
450
451
	/**
452
	 * Transforms URL to canonical form.
453
	 * @return static
454
	 */
455
	public function canonicalize()
456
	{
457
		$this->path = preg_replace_callback(
458
			'#[^!$&\'()*+,/:;=@%]+#',
459
			function ($m) { return rawurlencode($m[0]);
460
			},
461
			self::unescape($this->path, '%/')
462
		);
463
		$this->host = strtolower($this->host);
464
		return $this;
465
	}
466
467
468
	/**
469
	 * @return string
470
	 */
471 1
	public function jsonSerialize()
472
	{
473 1
		return $this->getAbsoluteUrl();
474
	}
475
476
477
	/**
478
	 * Similar to rawurldecode, but preserves reserved chars encoded.
479
	 * @param  string $s decode
480
	 * @param  string $reserved characters
481
	 * @return string
482
	 */
483 1
	public static function unescape($s, $reserved = '%;/?:@&=+$,')
484
	{
485
		// reserved (@see RFC 2396) = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
486
		// within a path segment, the characters "/", ";", "=", "?" are reserved
487
		// within a query component, the characters ";", "/", "?", ":", "@", "&", "=", "+", ",", "$" are reserved.
488 1
		if ($reserved !== '') {
489 1
			$s = preg_replace_callback(
490 1
				'#%(' . substr(chunk_split(bin2hex($reserved), 2, '|'), 0, -1) . ')#i',
491
				function ($m) { return '%25' . strtoupper($m[1]);
492 1
				},
493 1
				$s
494
			);
495
		}
496 1
		return rawurldecode($s);
497
	}
498
499
500
	/**
501
	 * Parses query string.
502
	 * @return array
503
	 */
504 19
	public static function parseQuery(string $s)
505
	{
506 19
		parse_str($s, $res);
507 19
		return $res;
508
	}
509
510
}
511