1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace HughCube\PUrl; |
4
|
|
|
|
5
|
|
|
use HughCube\PUrl\Exceptions\InvalidArgumentException; |
6
|
|
|
use Psr\Http\Message\UriInterface; |
7
|
|
|
|
8
|
|
|
class Url implements UriInterface |
9
|
|
|
{ |
10
|
|
|
/** |
11
|
|
|
* @var int[] |
12
|
|
|
*/ |
13
|
|
|
private $schemes = [ |
14
|
|
|
'http' => 80, |
15
|
|
|
'https' => 443, |
16
|
|
|
]; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* @var string|null url scheme |
20
|
|
|
*/ |
21
|
|
|
private $scheme; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* @var string|null url host |
25
|
|
|
*/ |
26
|
|
|
private $host; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* @var int|null url port |
30
|
|
|
*/ |
31
|
|
|
private $port; |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* @var string|null url user |
35
|
|
|
*/ |
36
|
|
|
private $user; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* @var string|null url pass |
40
|
|
|
*/ |
41
|
|
|
private $pass; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* @var string|null url path |
45
|
|
|
*/ |
46
|
|
|
private $path; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* @var string|null url query string |
50
|
|
|
*/ |
51
|
|
|
private $query; |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* @var string|null url fragment |
55
|
|
|
*/ |
56
|
|
|
private $fragment; |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* 获取实例. |
60
|
|
|
* |
61
|
|
|
* @param null|UriInterface $url |
62
|
|
|
* |
63
|
|
|
* @return static |
64
|
|
|
*/ |
65
|
12 |
|
public static function instance($url = null) |
66
|
|
|
{ |
67
|
12 |
|
return new static($url); |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* Url constructor. |
72
|
|
|
* |
73
|
|
|
* @param null|string|string[]|UriInterface $url |
74
|
|
|
*/ |
75
|
12 |
|
final protected function __construct($url = null) |
76
|
|
|
{ |
77
|
12 |
|
if ($url instanceof UriInterface) { |
78
|
4 |
|
$this->parsePsrUrl($url); |
79
|
12 |
|
} elseif (is_string($url)) { |
80
|
9 |
|
$this->parseStringUrl($url); |
81
|
5 |
|
} elseif (is_array($url)) { |
82
|
4 |
|
$this->parseArrayUrl($url); |
83
|
|
|
} |
84
|
11 |
|
} |
85
|
|
|
|
86
|
|
|
/** |
87
|
|
|
* 解析 Psr 标准库的url. |
88
|
|
|
* |
89
|
|
|
* @param UriInterface $url |
90
|
|
|
* |
91
|
|
|
* @return $this |
92
|
|
|
*/ |
93
|
4 |
|
private function parsePsrUrl(UriInterface $url) |
94
|
|
|
{ |
95
|
4 |
|
$this->scheme = empty($scheme = $url->getScheme()) ? null : $scheme; |
96
|
4 |
|
$this->host = empty($host = $url->getHost()) ? null : $host; |
97
|
4 |
|
$this->port = empty($port = $url->getPort()) ? null : $port; |
98
|
4 |
|
$this->path = empty($path = $url->getPath()) ? null : $path; |
99
|
4 |
|
$this->query = empty($query = $url->getQuery()) ? null : $query; |
100
|
4 |
|
$this->fragment = empty($fragment = $url->getFragment()) ? null : $fragment; |
101
|
|
|
|
102
|
4 |
|
$user = $this->getUserInfo(); |
103
|
4 |
|
$user = explode(':', $user); |
104
|
4 |
|
$this->user = (is_array($user) && isset($user[0])) ? $user[0] : null; |
105
|
4 |
|
$this->pass = (is_array($user) && isset($user[1])) ? $user[1] : null; |
106
|
|
|
|
107
|
4 |
|
return $this; |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* 解析字符串url. |
112
|
|
|
* |
113
|
|
|
* @param string $url |
114
|
|
|
* |
115
|
|
|
* @return $this |
116
|
|
|
*/ |
117
|
9 |
|
private function parseStringUrl($url) |
118
|
|
|
{ |
119
|
9 |
|
if (!static::isUrlString($url)) { |
120
|
1 |
|
throw new InvalidArgumentException('the parameter must be a url'); |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
/** @var string[] $parts */ |
124
|
8 |
|
$parts = parse_url($url); |
125
|
8 |
|
$this->parseArrayUrl($parts); |
126
|
|
|
|
127
|
8 |
|
return $this; |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
/** |
131
|
|
|
* 解析数组url. |
132
|
|
|
* |
133
|
|
|
* @param string[]|int[] $parts |
134
|
|
|
* |
135
|
|
|
* @return $this |
136
|
|
|
*/ |
137
|
10 |
|
private function parseArrayUrl(array $parts) |
138
|
|
|
{ |
139
|
10 |
|
$this->scheme = isset($parts['scheme']) ? $parts['scheme'] : null; |
|
|
|
|
140
|
10 |
|
$this->host = isset($parts['host']) ? $parts['host'] : null; |
|
|
|
|
141
|
10 |
|
$this->port = isset($parts['port']) ? $parts['port'] : null; |
|
|
|
|
142
|
10 |
|
$this->user = isset($parts['user']) ? $parts['user'] : null; |
|
|
|
|
143
|
10 |
|
$this->pass = isset($parts['pass']) ? $parts['pass'] : null; |
|
|
|
|
144
|
10 |
|
$this->path = isset($parts['path']) ? $parts['path'] : null; |
|
|
|
|
145
|
10 |
|
$this->query = isset($parts['query']) ? $parts['query'] : null; |
|
|
|
|
146
|
10 |
|
$this->fragment = isset($parts['fragment']) ? $parts['fragment'] : null; |
|
|
|
|
147
|
|
|
|
148
|
10 |
|
return $this; |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
/** |
152
|
|
|
* 填充 Psr 标准库的url. |
153
|
|
|
* |
154
|
|
|
* @param UriInterface $url |
155
|
|
|
* |
156
|
|
|
* @return UriInterface |
157
|
|
|
*/ |
158
|
|
|
public function fillPsrUri(UriInterface $url) |
159
|
|
|
{ |
160
|
|
|
return $url->withScheme($this->getScheme()) |
161
|
|
|
->withUserInfo($this->getUser(), $this->getPass()) |
162
|
|
|
->withHost($this->getHost()) |
163
|
|
|
->withPort($this->getPort()) |
164
|
|
|
->withPath($this->getPath()) |
165
|
|
|
->withQuery($this->getQuery()) |
166
|
|
|
->withFragment($this->getFragment()); |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
/** |
170
|
|
|
* {@inheritdoc} |
171
|
|
|
*/ |
172
|
10 |
|
public function getScheme() |
173
|
|
|
{ |
174
|
10 |
|
return strval($this->scheme); |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
/** |
178
|
|
|
* {@inheritdoc} |
179
|
|
|
*/ |
180
|
8 |
|
public function getAuthority() |
181
|
|
|
{ |
182
|
8 |
|
$authority = $host = $this->getHost(); |
183
|
8 |
|
if (empty($host)) { |
184
|
|
|
return $authority; |
185
|
|
|
} |
186
|
|
|
|
187
|
8 |
|
$userInfo = $this->getUserInfo(); |
188
|
8 |
|
if (!empty($userInfo)) { |
189
|
|
|
$authority = "{$userInfo}@{$authority}"; |
190
|
|
|
} |
191
|
|
|
|
192
|
8 |
|
$port = $this->getPort(); |
193
|
8 |
|
if ($this->isNonStandardPort() && !empty($port)) { |
194
|
|
|
$authority = "{$authority}:{$port}"; |
195
|
|
|
} |
196
|
|
|
|
197
|
8 |
|
return $authority; |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
/** |
201
|
|
|
* {@inheritdoc} |
202
|
|
|
*/ |
203
|
8 |
|
public function getUserInfo() |
204
|
|
|
{ |
205
|
8 |
|
$userInfo = $user = $this->getUser(); |
206
|
8 |
|
if (empty($user)) { |
207
|
8 |
|
return $userInfo; |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
$pass = $this->getPass(); |
211
|
|
|
if (!empty($pass)) { |
212
|
|
|
$userInfo = "{$userInfo}:{$pass}"; |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
return $userInfo; |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
/** |
219
|
|
|
* 获取 url user. |
220
|
|
|
* |
221
|
|
|
* @return string |
222
|
|
|
*/ |
223
|
8 |
|
public function getUser() |
224
|
|
|
{ |
225
|
8 |
|
return strval($this->user); |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
/** |
229
|
|
|
* 获取 url pass. |
230
|
|
|
* |
231
|
|
|
* @return string |
232
|
|
|
*/ |
233
|
2 |
|
public function getPass() |
234
|
|
|
{ |
235
|
2 |
|
return strval($this->pass); |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
/** |
239
|
|
|
* {@inheritdoc} |
240
|
|
|
*/ |
241
|
8 |
|
public function getHost() |
242
|
|
|
{ |
243
|
8 |
|
return strval($this->host); |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
/** |
247
|
|
|
* {@inheritdoc} |
248
|
|
|
*/ |
249
|
8 |
|
public function getPort() |
250
|
|
|
{ |
251
|
8 |
|
if (!empty($this->port)) { |
252
|
4 |
|
return $this->port; |
253
|
|
|
} |
254
|
|
|
|
255
|
8 |
|
$scheme = $this->getScheme(); |
256
|
|
|
|
257
|
8 |
|
return isset($this->schemes[$scheme]) ? $this->schemes[$scheme] : null; |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
/** |
261
|
|
|
* {@inheritdoc} |
262
|
|
|
*/ |
263
|
8 |
|
public function getPath() |
264
|
|
|
{ |
265
|
8 |
|
if (empty($this->path)) { |
266
|
|
|
return ''; |
267
|
|
|
} |
268
|
|
|
|
269
|
8 |
|
return '/' === substr($this->path, 0, 1) ? $this->path : "/{$this->path}"; |
270
|
|
|
} |
271
|
|
|
|
272
|
|
|
/** |
273
|
|
|
* {@inheritdoc} |
274
|
|
|
*/ |
275
|
8 |
|
public function getQuery() |
276
|
|
|
{ |
277
|
8 |
|
return strval($this->query); |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
/** |
281
|
|
|
* 获取query数组. |
282
|
|
|
* |
283
|
|
|
* @return array |
284
|
|
|
*/ |
285
|
2 |
|
public function getQueryArray() |
286
|
|
|
{ |
287
|
2 |
|
$query = $this->getQuery(); |
288
|
|
|
|
289
|
2 |
|
$queryArray = []; |
290
|
2 |
|
if (!empty($query)) { |
291
|
2 |
|
parse_str($query, $queryArray); |
292
|
|
|
} |
293
|
|
|
|
294
|
2 |
|
return is_array($queryArray) ? $queryArray : []; |
295
|
|
|
} |
296
|
|
|
|
297
|
|
|
/** |
298
|
|
|
* 是否存在query的key. |
299
|
|
|
* |
300
|
|
|
* @param string $key |
301
|
|
|
* |
302
|
|
|
* @return bool |
303
|
|
|
*/ |
304
|
|
|
public function hasQueryKey($key) |
305
|
|
|
{ |
306
|
|
|
$queryArray = $this->getQueryArray(); |
307
|
|
|
|
308
|
|
|
return array_key_exists($key, $queryArray); |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
/** |
312
|
|
|
* 是否存在query的key. |
313
|
|
|
* |
314
|
|
|
* @param string $key |
315
|
|
|
* @param mixed $default |
316
|
|
|
* |
317
|
|
|
* @return array|string |
318
|
|
|
*/ |
319
|
|
|
public function getQueryValue($key, $default = null) |
320
|
|
|
{ |
321
|
|
|
$queryArray = $this->getQueryArray(); |
322
|
|
|
|
323
|
|
|
return array_key_exists($key, $queryArray) ? $queryArray[$key] : $default; |
324
|
|
|
} |
325
|
|
|
|
326
|
|
|
/** |
327
|
|
|
* {@inheritdoc} |
328
|
|
|
*/ |
329
|
8 |
|
public function getFragment() |
330
|
|
|
{ |
331
|
8 |
|
return strval($this->fragment); |
332
|
|
|
} |
333
|
|
|
|
334
|
|
|
/** |
335
|
|
|
* Return the string representation as a URI reference. |
336
|
|
|
* |
337
|
|
|
* @return string |
338
|
|
|
*/ |
339
|
8 |
|
public function toString() |
340
|
|
|
{ |
341
|
8 |
|
$url = ''; |
342
|
|
|
|
343
|
8 |
|
$scheme = $this->getScheme(); |
344
|
8 |
|
if (!empty($scheme)) { |
345
|
8 |
|
$url = "{$scheme}://{$url}"; |
346
|
|
|
} |
347
|
|
|
|
348
|
8 |
|
$authority = $this->getAuthority(); |
349
|
8 |
|
if (!empty($authority)) { |
350
|
8 |
|
$url = "{$url}{$authority}"; |
351
|
|
|
} |
352
|
|
|
|
353
|
8 |
|
$path = $this->getPath(); |
354
|
8 |
|
if (!empty($path)) { |
355
|
8 |
|
$url = "{$url}{$path}"; |
356
|
|
|
} |
357
|
|
|
|
358
|
8 |
|
$query = $this->getQuery(); |
359
|
8 |
|
if (!empty($query)) { |
360
|
8 |
|
$url = "{$url}?{$query}"; |
361
|
|
|
} |
362
|
|
|
|
363
|
8 |
|
$fragment = $this->getFragment(); |
364
|
8 |
|
if (!empty($fragment)) { |
365
|
4 |
|
$url = "{$url}#{$fragment}"; |
366
|
|
|
} |
367
|
|
|
|
368
|
8 |
|
return $url; |
369
|
|
|
} |
370
|
|
|
|
371
|
|
|
/** |
372
|
|
|
* {@inheritdoc} |
373
|
|
|
*/ |
374
|
|
|
public function withScheme($scheme) |
375
|
|
|
{ |
376
|
|
|
$new = clone $this; |
377
|
|
|
$new->scheme = $scheme; |
378
|
|
|
|
379
|
|
|
return $new; |
380
|
|
|
} |
381
|
|
|
|
382
|
|
|
/** |
383
|
|
|
* {@inheritdoc} |
384
|
|
|
*/ |
385
|
|
|
public function withUserInfo($user, $password = null) |
386
|
|
|
{ |
387
|
|
|
$new = clone $this; |
388
|
|
|
$new->user = $user; |
389
|
|
|
$new->pass = $password; |
390
|
|
|
|
391
|
|
|
return $new; |
392
|
|
|
} |
393
|
|
|
|
394
|
|
|
/** |
395
|
|
|
* {@inheritdoc} |
396
|
|
|
*/ |
397
|
|
|
public function withHost($host) |
398
|
|
|
{ |
399
|
|
|
$new = clone $this; |
400
|
|
|
$new->host = $host; |
401
|
|
|
|
402
|
|
|
return $new; |
403
|
|
|
} |
404
|
|
|
|
405
|
|
|
/** |
406
|
|
|
* {@inheritdoc} |
407
|
|
|
*/ |
408
|
|
|
public function withPort($port) |
409
|
|
|
{ |
410
|
|
|
$new = clone $this; |
411
|
|
|
$new->port = $port; |
412
|
|
|
|
413
|
|
|
return $new; |
414
|
|
|
} |
415
|
|
|
|
416
|
|
|
/** |
417
|
|
|
* {@inheritdoc} |
418
|
|
|
*/ |
419
|
|
|
public function withPath($path) |
420
|
|
|
{ |
421
|
|
|
$new = clone $this; |
422
|
|
|
$new->path = $path; |
423
|
|
|
|
424
|
|
|
return $new; |
425
|
|
|
} |
426
|
|
|
|
427
|
|
|
/** |
428
|
|
|
* {@inheritdoc} |
429
|
|
|
*/ |
430
|
|
|
public function withQuery($query) |
431
|
|
|
{ |
432
|
|
|
$new = clone $this; |
433
|
|
|
$new->query = $query; |
434
|
|
|
|
435
|
|
|
return $new; |
436
|
|
|
} |
437
|
|
|
|
438
|
|
|
/** |
439
|
|
|
* Return an instance with the specified query array. |
440
|
|
|
* |
441
|
|
|
* @param array $queryArray |
442
|
|
|
* |
443
|
|
|
* @return static |
444
|
|
|
*/ |
445
|
|
|
public function withQueryArray(array $queryArray) |
446
|
|
|
{ |
447
|
|
|
return $this->withQuery(http_build_query($queryArray)); |
448
|
|
|
} |
449
|
|
|
|
450
|
|
|
/** |
451
|
|
|
* Create a new URI with a specific query string value removed. |
452
|
|
|
* |
453
|
|
|
* @param string|int $key |
454
|
|
|
* |
455
|
|
|
* @return static |
456
|
|
|
*/ |
457
|
|
|
public function withoutQueryValue($key) |
458
|
|
|
{ |
459
|
|
|
$queryArray = $this->getQueryArray(); |
460
|
|
|
|
461
|
|
|
if (isset($queryArray[$key])) { |
462
|
|
|
unset($queryArray[$key]); |
463
|
|
|
} |
464
|
|
|
|
465
|
|
|
return $this->withQueryArray($queryArray); |
466
|
|
|
} |
467
|
|
|
|
468
|
|
|
/** |
469
|
|
|
* Create a new URI with a specific query string value. |
470
|
|
|
* |
471
|
|
|
* @param string $key |
472
|
|
|
* @param string|int $value |
473
|
|
|
* |
474
|
|
|
* @return static |
475
|
|
|
*/ |
476
|
|
|
public function withQueryValue($key, $value) |
477
|
|
|
{ |
478
|
|
|
$queryArray = $this->getQueryArray(); |
479
|
|
|
$queryArray[$key] = $value; |
480
|
|
|
|
481
|
|
|
return $this->withQueryArray($queryArray); |
482
|
|
|
} |
483
|
|
|
|
484
|
|
|
/** |
485
|
|
|
* {@inheritdoc} |
486
|
|
|
*/ |
487
|
|
|
public function withFragment($fragment) |
488
|
|
|
{ |
489
|
|
|
$new = clone $this; |
490
|
|
|
$new->fragment = $fragment; |
491
|
|
|
|
492
|
|
|
return $new; |
493
|
|
|
} |
494
|
|
|
|
495
|
|
|
/** |
496
|
|
|
* {@inheritdoc} |
497
|
|
|
*/ |
498
|
2 |
|
public function __toString() |
499
|
|
|
{ |
500
|
2 |
|
return $this->toString(); |
501
|
|
|
} |
502
|
|
|
|
503
|
|
|
/** |
504
|
|
|
* Is a given port non-standard for the current scheme? |
505
|
|
|
* |
506
|
|
|
* @return bool |
507
|
|
|
*/ |
508
|
8 |
|
private function isNonStandardPort() |
509
|
|
|
{ |
510
|
8 |
|
if (!$this->scheme && $this->port) { |
|
|
|
|
511
|
|
|
return true; |
512
|
|
|
} |
513
|
|
|
|
514
|
8 |
|
if (!$this->host || !$this->port) { |
|
|
|
|
515
|
6 |
|
return false; |
516
|
|
|
} |
517
|
|
|
|
518
|
4 |
|
return !isset($this->schemes[$this->scheme]) |
519
|
4 |
|
|| $this->port !== $this->schemes[$this->scheme]; |
520
|
|
|
} |
521
|
|
|
|
522
|
|
|
/** |
523
|
|
|
* is url string. |
524
|
|
|
* |
525
|
|
|
* @param mixed $url |
526
|
|
|
* |
527
|
|
|
* @return bool |
528
|
|
|
*/ |
529
|
9 |
|
public static function isUrlString($url) |
530
|
|
|
{ |
531
|
9 |
|
return false !== filter_var($url, FILTER_VALIDATE_URL); |
532
|
|
|
} |
533
|
|
|
} |
534
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.