本文將闡述如何用PHP以正確的姿勢獲取URL的信息,並將其封裝成類,方便複用。

URL#

首先來了解一下URL的文法:

scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]

中括號表示的是可選項。

NameExplanation
scheme傳輸協議
user, password訪問資源需要的憑證信息
host服務器
port端口
path路徑
query查詢
fragment片段
 1                        hierarchical part
 2            ┌───────────────────┴─────────────────────┐
 3                        authority               path
 4            ┌───────────────┴───────────────┐┌───┴────┐
 5      abc://username:password@example.com:123/path/data?key=value&key2=value2#fragid1
 6      └┬┘   └───────┬───────┘ └────┬────┘ └┬┘           └─────────┬─────────┘ └──┬──┘
 7    scheme  user information     host     port                  query         fragment
 8    
 9      urn:example:mammal:monotreme:echidna
10      └┬┘ └──────────────┬───────────────┘
11    scheme              path

函數#

在瞭解URL的文法之後,我們很容易編寫函數去獲取URL的信息,但是本文並不打算編寫獨立的函數,
因為PHP已經提供了內置的函數 parse_urlparse_str 來實現這一目的。

parse_url#

1mixed parse_url ( string $url [, int $component = -1 ] )

parse_url 解析一個URL,並返回一個包含URL各種組成部分的關聯數組(鍵名和URL文法中的一致,比如:schemehostport等)。

當指定了第二個參數 component,則會返回指定組成部分的值,此時返回的是字符串或者數值(PHP_URL_PORT)。

官方文檔:https://secure.php.net/manual/zh/function.parse-url.php

parse_str#

1void parse_str ( string $encoded_string [, array &$result ] )

parse_str 將字符串解析成多個變量,第二個參數 $result 用於存儲變量的鍵值。

這裡有點小疑惑,因為可以看到 $result 是可選的,且該函數沒有返回值,如果不設置 $result,那麼這函數的意義是什麼呢?

不過官方文檔也標註了:極度不建議不設置 $result 的情況下使用該函數,並且在 PHP 7.2 中將廢棄不設置參數的行為。

官方文檔:https://secure.php.net/manual/zh/function.parse-str.php

實現#

通過上述的 parse_urlparse_str 函數,可以輕鬆的獲取URL的信息:

 1// URL
 2$url = 'https://example.com/user?name=foo';
 3
 4// 獲取URL組成部分
 5$components = parse_url($url);
 6print_r($components);
 7
 8// 獲取查詢字符串
 9$query = isset($components['query']) ? $components['query'] : '';
10print_r($query);
11
12// 獲取查詢參數數組
13parse_str($query, $params);
14print_r($params);

輸出

 1    Array
 2    (
 3        [scheme] => https
 4        [host] => example.com
 5        [path] => /user
 6        [query] => name=foo
 7    )
 8    
 9    name=foo
10    
11    Array
12    (
13        [name] => foo
14    )

Class#

最後,將此封裝成類,方便複用。

 1/**
 2 * Class URL
 3 *
 4 * @property string|null scheme
 5 *
 6 * @property string|null user
 7 *
 8 * @property string|null pass
 9 *
10 * @property string|null host
11 *
12 * @property integer|null port
13 *
14 * @property string|null path
15 *
16 * @property string|null query
17 *
18 * @property string|null fragment
19 */
20class URL
21{
22    private $data;
23
24    private $queryParams;
25
26    public function __construct($url)
27    {
28        $this->data = parse_url($url);
29        if ($this->data === false) {
30            throw new \InvalidArgumentException("invalid 
31        }
32    }
33
34    public function __get($name)
35    {
36        return isset($this->data[$name]) ? $this->data[$name] : null;
37    }
38
39    public function getQueryParams()
40    {
41        if ($this->queryParams === null) {
42            $query = $this->query;
43            if (empty($query)) {
44                $this->queryParams = [];
45            } else {
46                parse_str($query, $this->queryParams);
47            }
48        }
49
50        return $this->queryParams;
51    }
52}