遞迴匿名函式手動實現 http_build_query 系統函式

guanguans發表於2022-07-12
最近覺得 http_build_query 函式的功能蠻神奇的。可以將任意一個複雜陣列轉換成一個複雜的 URL 查詢字串。於是自己嘗試手動實現了一下(user_http_build_query)。

版本一、主函式遞迴額外輔助函式實現

/**
 * http_build_query 的實現。
 *
 * @param  array  $queryPayload
 * @param  string  $numericPrefix
 * @param  string  $argSeparator
 * @param  int  $encType
 *
 * @return string
 */
function user_http_build_query(array $queryPayload, string $numericPrefix = '', string $argSeparator = '&', int $encType = PHP_QUERY_RFC1738): string
{
    reset($queryPayload);
    $queryStr = '';
    foreach ($queryPayload as $k => $v) {
        // 特殊值處理
        if ($v === null) {
            continue;
        }
        if ($v === 0 || $v === false) {
            $v = '0';
        }

        // 為了對資料進行解碼時獲取合法的變數名
        if (is_numeric($k) && ! is_string($k)) {
            $k = $numericPrefix . $k;
        }

        $queryStr .= is_scalar($v)
            ? sprintf("%s=%s$argSeparator", $encType === PHP_QUERY_RFC3986 ? rawurlencode($k) : urlencode($k), $encType === PHP_QUERY_RFC3986 ? rawurlencode($v) : urlencode($v))
            : to_query_str($k, $v, $argSeparator, $encType);
    }

    return substr($queryStr, 0, -strlen($argSeparator));
}

/**
 * 轉換值是非標量的情況
 *
 * @param  string  $key
 * @param  array|object  $value
 * @param  string  $argSeparator
 * @param  int  $encType
 *
 * @return string
 */
function to_query_str(string $key, $value, string $argSeparator, int $encType): string
{
    $queryStr = '';
    foreach ($value as $k => $v) {
        // 特殊值處理
        if ($v === null) {
            continue;
        }
        if ($v === 0 || $v === false) {
            $v = '0';
        }

        $fullKey = "{$key}[{$k}]";
        $queryStr .= is_scalar($v)
            ? sprintf("%s=%s$argSeparator", $encType === PHP_QUERY_RFC3986 ? rawurlencode($fullKey) : urlencode($fullKey), $encType === PHP_QUERY_RFC3986 ? rawurlencode($v) : urlencode($v))
            : to_query_str($fullKey, $v, $argSeparator, $encType); // 遞迴呼叫
    }

    return $queryStr;
}

版本二、單函式遞迴匿名函式實現

用兩個函式去實現一個函式的功能,顯然是不太滿意的。稍微改動一下,提取輔助函式為匿名函式到主函式內部,遞迴匿名函式就可以了。
/**
 * http_build_query 的實現。
 *
 * @param  array  $queryPayload
 * @param  string  $numericPrefix
 * @param  string  $argSeparator
 * @param  int  $encType
 *
 * @return string
 */
function user_http_build_query(array $queryPayload, string $numericPrefix = '', string $argSeparator = '&', int $encType = PHP_QUERY_RFC1738): string
{
    /**
     * 轉換值是非標量的情況
     *
     * @param  string  $key
     * @param  array|object  $value
     * @param  string  $argSeparator
     * @param  int  $encType
     *
     * @return string
     */
    $toQueryStr = static function (string $key, $value, string $argSeparator, int $encType) use (&$toQueryStr): string{
        $queryStr = '';
        foreach ($value as $k => $v) {
            // 特殊值處理
            if ($v === null) {
                continue;
            }
            if ($v === 0 || $v === false) {
                $v = '0';
            }

            $fullKey = "{$key}[{$k}]";
            $queryStr .= is_scalar($v)
                ? sprintf("%s=%s$argSeparator", $encType === PHP_QUERY_RFC3986 ? rawurlencode($fullKey) : urlencode($fullKey), $encType === PHP_QUERY_RFC3986 ? rawurlencode($v) : urlencode($v))
                : $toQueryStr($fullKey, $v, $argSeparator, $encType); // 遞迴呼叫
        }

        return $queryStr;
    };

    reset($queryPayload);
    $queryStr = '';
    foreach ($queryPayload as $k => $v) {
        // 特殊值處理
        if ($v === null) {
            continue;
        }
        if ($v === 0 || $v === false) {
            $v = '0';
        }

        // 為了對資料進行解碼時獲取合法的變數名
        if (is_numeric($k) && ! is_string($k)) {
            $k = $numericPrefix . $k;
        }

        $queryStr .= is_scalar($v)
            ? sprintf("%s=%s$argSeparator", $encType === PHP_QUERY_RFC3986 ? rawurlencode($k) : urlencode($k), $encType === PHP_QUERY_RFC3986 ? rawurlencode($v) : urlencode($v))
            : $toQueryStr($k, $v, $argSeparator, $encType);
    }

    return substr($queryStr, 0, -strlen($argSeparator));
}

測試

$queryPayload = [
     1 => 'a',
     '10' => 'b',
     '01' => 'c',
     'keyO1' => null,
     'keyO2' => false,
     'keyO3' => true,
     'keyO4' => 0,
     'keyO5' => 1,
     'keyO6' => 0.0,
     'keyO7' => 0.1,
     'keyO8' => [],
     'keyO9' => '',
     'key10' => new \stdClass(),
     'pastimes' => ['golf', 'opera', 'poker', 'rap'],
     'user' => [
         'name' => 'Bob Smith',
         'age' => 47,
         'sex' => 'M',
         'dob' => '5/12/1956'
     ],
     'children' => [
         'sally' => ['age' => 8, 'sex' => null],
         'bobby' => ['sex' => 'M', 'age' => 12],
     ],
 ];

dd(
    $queryStr1 = http_build_query($queryPayload),
    $queryStr2 = user_http_build_query($queryPayload),
    $queryStr3 = urldecode($queryStr1),
    $queryStr4 = urldecode($queryStr2),
    $queryStr1 === $queryStr2,
    $queryStr3 === $queryStr4,
);

// "1=a&10=b&01=c&keyO2=0&keyO3=1&keyO4=0&keyO5=1&keyO6=0&keyO7=0.1&keyO9=&pastimes%5B0%5D=golf&pastimes%5B1%5D=opera&pastimes%5B2%5D=poker&pastimes%5B3%5D=rap&user%5Bname%5D=Bob+Smith&user%5Bage%5D=47&user%5Bsex%5D=M&user%5Bdob%5D=5%2F12%2F1956&children%5Bsally%5D%5Bage%5D=8&children%5Bbobby%5D%5Bsex%5D=M&children%5Bbobby%5D%5Bage%5D=12"
// "1=a&10=b&01=c&keyO2=0&keyO3=1&keyO4=0&keyO5=1&keyO6=0&keyO7=0.1&keyO9=&pastimes%5B0%5D=golf&pastimes%5B1%5D=opera&pastimes%5B2%5D=poker&pastimes%5B3%5D=rap&user%5Bname%5D=Bob+Smith&user%5Bage%5D=47&user%5Bsex%5D=M&user%5Bdob%5D=5%2F12%2F1956&children%5Bsally%5D%5Bage%5D=8&children%5Bbobby%5D%5Bsex%5D=M&children%5Bbobby%5D%5Bage%5D=12"
// "1=a&10=b&01=c&keyO2=0&keyO3=1&keyO4=0&keyO5=1&keyO6=0&keyO7=0.1&keyO9=&pastimes[0]=golf&pastimes[1]=opera&pastimes[2]=poker&pastimes[3]=rap&user[name]=Bob Smith&user[age]=47&user[sex]=M&user[dob]=5/12/1956&children[sally][age]=8&children[bobby][sex]=M&children[bobby][age]=12"
// "1=a&10=b&01=c&keyO2=0&keyO3=1&keyO4=0&keyO5=1&keyO6=0&keyO7=0.1&keyO9=&pastimes[0]=golf&pastimes[1]=opera&pastimes[2]=poker&pastimes[3]=rap&user[name]=Bob Smith&user[age]=47&user[sex]=M&user[dob]=5/12/1956&children[sally][age]=8&children[bobby][sex]=M&children[bobby][age]=12"
// true
// true

原文連結

相關文章