Passed
Push — master ( 9c2aa3...2ab384 )
by hugh
06:43
created

Url::parse()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 2
Bugs 0 Features 2
Metric Value
eloc 4
c 2
b 0
f 2
dl 0
loc 7
ccs 0
cts 4
cp 0
rs 10
cc 2
nc 2
nop 1
crap 6
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
     * @var string|null url scheme
19
     */
20
    private $scheme;
21
    /**
22
     * @var string|null url host
23
     */
24
    private $host;
25
    /**
26
     * @var int|null url port
27
     */
28
    private $port;
29
    /**
30
     * @var string|null url user
31
     */
32
    private $user;
33
    /**
34
     * @var string|null url pass
35
     */
36
    private $pass;
37
    /**
38
     * @var string|null url path
39
     */
40
    private $path;
41
    /**
42
     * @var string|null url query string
43
     */
44
    private $query;
45
    /**
46
     * @var string|null url fragment
47
     */
48
    private $fragment;
49
50
    /**
51
     * 获取实例.
52
     *
53
     * @param  null|UriInterface  $url
54
     *
55
     * @return static
56
     */
57 13
    public static function instance($url = null)
58
    {
59 13
        return new static($url);
60
    }
61
62
    /**
63
     * @param  string  $url
64
     * @return Url|null
65
     */
66
    public static function parse(string $url)
67
    {
68
        try {
69
            return static::instance($url);
0 ignored issues
show
Bug introduced by
$url of type string is incompatible with the type Psr\Http\Message\UriInterface|null expected by parameter $url of HughCube\PUrl\Url::instance(). ( Ignorable by Annotation )

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

69
            return static::instance(/** @scrutinizer ignore-type */ $url);
Loading history...
70
        } catch (\Throwable $exception) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
71
        }
72
        return null;
73
    }
74
75
    /**
76
     * Url constructor.
77
     *
78
     * @param  null|string|string[]|UriInterface  $url
79
     */
80 13
    final protected function __construct($url = null)
81
    {
82 13
        if ($url instanceof UriInterface) {
83 4
            $this->parsePsrUrl($url);
84 13
        } elseif (is_string($url)) {
85 10
            $this->parseStringUrl($url);
86 5
        } elseif (is_array($url)) {
87 4
            $this->parseArrayUrl($url);
88
        }
89 12
    }
90
91
    /**
92
     * 解析 Psr 标准库的url.
93
     *
94
     * @param  UriInterface  $url
95
     *
96
     * @return $this
97
     */
98 4
    private function parsePsrUrl(UriInterface $url)
99
    {
100 4
        $this->scheme = empty($scheme = $url->getScheme()) ? null : $scheme;
101 4
        $this->host = empty($host = $url->getHost()) ? null : $host;
102 4
        $this->port = empty($port = $url->getPort()) ? null : $port;
103 4
        $this->path = empty($path = $url->getPath()) ? null : $path;
104 4
        $this->query = empty($query = $url->getQuery()) ? null : $query;
105 4
        $this->fragment = empty($fragment = $url->getFragment()) ? null : $fragment;
106 4
        $user = $this->getUserInfo();
107 4
        $user = explode(':', $user);
108 4
        $this->user = (is_array($user) && isset($user[0])) ? $user[0] : null;
109 4
        $this->pass = (is_array($user) && isset($user[1])) ? $user[1] : null;
110 4
        return $this;
111
    }
112
113
    /**
114
     * 解析字符串url.
115
     *
116
     * @param  string  $url
117
     *
118
     * @return $this
119
     */
120 10
    private function parseStringUrl($url)
121
    {
122 10
        if (!static::isUrlString($url)) {
123 1
            throw new InvalidArgumentException('the parameter must be a url');
124
        }
125
        /** @var string[] $parts */
126 9
        $parts = parse_url($url);
127 9
        $this->parseArrayUrl($parts);
128 9
        return $this;
129
    }
130
131
    /**
132
     * 解析数组url.
133
     *
134
     * @param  string[]|int[]  $parts
135
     *
136
     * @return $this
137
     */
138 11
    private function parseArrayUrl(array $parts)
139
    {
140 11
        $this->scheme = isset($parts['scheme']) ? $parts['scheme'] : null;
141 11
        $this->host = isset($parts['host']) ? $parts['host'] : null;
142 11
        $this->port = isset($parts['port']) ? $parts['port'] : null;
143 11
        $this->user = isset($parts['user']) ? $parts['user'] : null;
144 11
        $this->pass = isset($parts['pass']) ? $parts['pass'] : null;
145 11
        $this->path = isset($parts['path']) ? $parts['path'] : null;
146 11
        $this->query = isset($parts['query']) ? $parts['query'] : null;
147 11
        $this->fragment = isset($parts['fragment']) ? $parts['fragment'] : null;
148 11
        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 8
        $userInfo = $this->getUserInfo();
187 8
        if (!empty($userInfo)) {
188
            $authority = "{$userInfo}@{$authority}";
189
        }
190 8
        $port = $this->getPort();
191 8
        if ($this->isNonStandardPort() && !empty($port)) {
192
            $authority = "{$authority}:{$port}";
193
        }
194 8
        return $authority;
195
    }
196
197
    /**
198
     * {@inheritdoc}
199
     */
200 8
    public function getUserInfo()
201
    {
202 8
        $userInfo = $user = $this->getUser();
203 8
        if (empty($user)) {
204 8
            return $userInfo;
205
        }
206
        $pass = $this->getPass();
207
        if (!empty($pass)) {
208
            $userInfo = "{$userInfo}:{$pass}";
209
        }
210
        return $userInfo;
211
    }
212
213
    /**
214
     * 获取 url user.
215
     *
216
     * @return string
217
     */
218 8
    public function getUser()
219
    {
220 8
        return strval($this->user);
221
    }
222
223
    /**
224
     * 获取 url pass.
225
     *
226
     * @return string
227
     */
228 2
    public function getPass()
229
    {
230 2
        return strval($this->pass);
231
    }
232
233
    /**
234
     * {@inheritdoc}
235
     */
236 9
    public function getHost()
237
    {
238 9
        return strval($this->host);
239
    }
240
241
    /**
242
     * {@inheritdoc}
243
     */
244 8
    public function getPort()
245
    {
246 8
        if (!empty($this->port)) {
247 4
            return $this->port;
248
        }
249 8
        $scheme = $this->getScheme();
250 8
        return isset($this->schemes[$scheme]) ? $this->schemes[$scheme] : null;
251
    }
252
253
    /**
254
     * {@inheritdoc}
255
     */
256 8
    public function getPath()
257
    {
258 8
        if (empty($this->path)) {
259
            return '';
260
        }
261 8
        return '/' === substr($this->path, 0, 1) ? $this->path : "/{$this->path}";
262
    }
263
264
    /**
265
     * {@inheritdoc}
266
     */
267 8
    public function getQuery()
268
    {
269 8
        return strval($this->query);
270
    }
271
272
    /**
273
     * 获取query数组.
274
     *
275
     * @return array
276
     */
277 2
    public function getQueryArray()
278
    {
279 2
        $query = $this->getQuery();
280 2
        $queryArray = [];
281 2
        if (!empty($query)) {
282 2
            parse_str($query, $queryArray);
283
        }
284 2
        return is_array($queryArray) ? $queryArray : [];
285
    }
286
287
    /**
288
     * 是否存在query的key.
289
     *
290
     * @param  string  $key
291
     *
292
     * @return bool
293
     */
294
    public function hasQueryKey($key)
295
    {
296
        $queryArray = $this->getQueryArray();
297
        return array_key_exists($key, $queryArray);
298
    }
299
300
    /**
301
     * 是否存在query的key.
302
     *
303
     * @param  string  $key
304
     * @param  mixed  $default
305
     *
306
     * @return array|string
307
     */
308
    public function getQueryValue($key, $default = null)
309
    {
310
        $queryArray = $this->getQueryArray();
311
        return array_key_exists($key, $queryArray) ? $queryArray[$key] : $default;
312
    }
313
314
    /**
315
     * {@inheritdoc}
316
     */
317 8
    public function getFragment()
318
    {
319 8
        return strval($this->fragment);
320
    }
321
322
    /**
323
     * Return the string representation as a URI reference.
324
     *
325
     * @return string
326
     */
327 8
    public function toString()
328
    {
329 8
        $url = '';
330 8
        $scheme = $this->getScheme();
331 8
        if (!empty($scheme)) {
332 8
            $url = "{$scheme}://{$url}";
333
        }
334 8
        $authority = $this->getAuthority();
335 8
        if (!empty($authority)) {
336 8
            $url = "{$url}{$authority}";
337
        }
338 8
        $path = $this->getPath();
339 8
        if (!empty($path)) {
340 8
            $url = "{$url}{$path}";
341
        }
342 8
        $query = $this->getQuery();
343 8
        if (!empty($query)) {
344 8
            $url = "{$url}?{$query}";
345
        }
346 8
        $fragment = $this->getFragment();
347 8
        if (!empty($fragment)) {
348 4
            $url = "{$url}#{$fragment}";
349
        }
350 8
        return $url;
351
    }
352
353
    /**
354
     * {@inheritdoc}
355
     */
356
    public function withScheme($scheme)
357
    {
358
        $new = clone $this;
359
        $new->scheme = $scheme;
360
        return $new;
361
    }
362
363
    /**
364
     * {@inheritdoc}
365
     */
366
    public function withUserInfo($user, $password = null)
367
    {
368
        $new = clone $this;
369
        $new->user = $user;
370
        $new->pass = $password;
371
        return $new;
372
    }
373
374
    /**
375
     * {@inheritdoc}
376
     */
377
    public function withHost($host)
378
    {
379
        $new = clone $this;
380
        $new->host = $host;
381
        return $new;
382
    }
383
384
    /**
385
     * {@inheritdoc}
386
     */
387
    public function withPort($port)
388
    {
389
        $new = clone $this;
390
        $new->port = $port;
391
        return $new;
392
    }
393
394
    /**
395
     * {@inheritdoc}
396
     */
397
    public function withPath($path)
398
    {
399
        $new = clone $this;
400
        $new->path = $path;
401
        return $new;
402
    }
403
404
    /**
405
     * {@inheritdoc}
406
     */
407
    public function withQuery($query)
408
    {
409
        $new = clone $this;
410
        $new->query = $query;
411
        return $new;
412
    }
413
414
    /**
415
     * Return an instance with the specified query array.
416
     *
417
     * @param  array  $queryArray
418
     *
419
     * @return static
420
     */
421
    public function withQueryArray(array $queryArray)
422
    {
423
        return $this->withQuery(http_build_query($queryArray));
424
    }
425
426
    /**
427
     * Create a new URI with a specific query string value removed.
428
     *
429
     * @param  string|int  $key
430
     *
431
     * @return static
432
     */
433
    public function withoutQueryValue($key)
434
    {
435
        $queryArray = $this->getQueryArray();
436
        if (isset($queryArray[$key])) {
437
            unset($queryArray[$key]);
438
        }
439
        return $this->withQueryArray($queryArray);
440
    }
441
442
    /**
443
     * Create a new URI with a specific query string value.
444
     *
445
     * @param  string  $key
446
     * @param  string|int  $value
447
     *
448
     * @return static
449
     */
450
    public function withQueryValue($key, $value)
451
    {
452
        $queryArray = $this->getQueryArray();
453
        $queryArray[$key] = $value;
454
        return $this->withQueryArray($queryArray);
455
    }
456
457
    /**
458
     * {@inheritdoc}
459
     */
460
    public function withFragment($fragment)
461
    {
462
        $new = clone $this;
463
        $new->fragment = $fragment;
464
        return $new;
465
    }
466
467
    /**
468
     * Check if host is matched
469
     *
470
     * @param  string  $pattern
471
     * @return bool
472
     */
473 1
    public function matchHost($pattern)
474
    {
475 1
        if (empty($pattern) || empty($this->getHost())) {
476
            return false;
477
        }
478
479 1
        if ($pattern == $this->getHost()) {
480
            return true;
481
        }
482
483 1
        $pattern = preg_quote($pattern, '#');
484 1
        $pattern = str_replace('\*', '.*', $pattern);
485 1
        $pattern = str_replace('\|', '|', $pattern);
486
487 1
        $pattern = '#^('.$pattern.')\z#u';
488
489 1
        return 1 == preg_match($pattern, $this->getHost());
490
    }
491
492
    /**
493
     * {@inheritdoc}
494
     */
495 2
    public function __toString()
496
    {
497 2
        return $this->toString();
498
    }
499
500
    /**
501
     * Is a given port non-standard for the current scheme?
502
     *
503
     * @return bool
504
     */
505 8
    private function isNonStandardPort()
506
    {
507 8
        if (!$this->scheme && $this->port) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->port of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
508
            return true;
509
        }
510 8
        if (!$this->host || !$this->port) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->port of type integer|null is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
511 6
            return false;
512
        }
513 4
        return !isset($this->schemes[$this->scheme])
514 4
            || $this->port !== $this->schemes[$this->scheme];
515
    }
516
517
    /**
518
     * is url string.
519
     *
520
     * @param  mixed  $url
521
     *
522
     * @return bool
523
     */
524 10
    public static function isUrlString($url)
525
    {
526 10
        return false !== filter_var($url, FILTER_VALIDATE_URL);
527
    }
528
}
529