递归匿名函数手动实现 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

原文链接

本作品采用《CC 协议》,转载必须注明作者和本文链接
No practice, no gain in one's wit. 我的 Gitub
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
未填写
文章
59
粉丝
133
喜欢
994
收藏
1354
排名:45
访问:15.5 万
私信
所有博文
社区赞助商