Skip to content

递归匿名函数手动实现 http_build_query 系统函数 #47

Open
@guanguans

Description

@guanguans
Owner

递归匿名函数手动实现 http_build_query 系统函数

最近觉得 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

原文链接

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @guanguans

        Issue actions

          递归匿名函数手动实现 http_build_query 系统函数 · Issue #47 · guanguans/guanguans.github.io