Passed
Push — master ( d2bd53...9c2aa3 )
by hugh
07:56
created

Url::matchHost()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4.128

Importance

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