PHP中数字索引数组的最短可能查询字符串

时间:2014-01-04 00:54:25

标签: php arrays query-string

我正在寻找最简洁的URL而不是最短的PHP代码。我不希望我的用户被编码数组时PHP创建的可怕URL吓到。

如果你只是通过http_build_query填充一个数组($fn),那么PHP会在查询字符串中做很多重复:

$fs = array(5, 12, 99);
$url = "http://$_SERVER[HTTP_HOST]/?" .
    http_build_query(array('c' => 'asdf', 'fs' => $fs));

结果$url

http://example.com/?c=asdf&fs[0]=5&fs[1]=12&fs[3]=99

如何将其降至最低(使用PHP或在PHP中轻松实现的方法)?

3 个答案:

答案 0 :(得分:5)

默认PHP方式

http_build_query的作用是将数组序列化为URL的常用方法。 PHP会自动在$_GET中反序列化它。

当想要仅序列化(非关联)整数数组时,您还有其他选项。

小数组

对于小型数组,转换为下划线列表非常方便有效。它由$fs = implode('_', $fs)完成。那么您的网址将如下所示:

http://example.com/?c=asdf&fs=5_12_99

缺点是您必须明确explode('_', $_GET['fs'])才能将值作为数组返回。

也可以使用其他分隔符。下划线被认为是字母数字,因此很少有特殊含义。在URL中,它通常用作空间替换(例如,通过MediaWiki)。在下划线文本中使用时很难区分。连字符是另一种常见的空间替代品。它也经常用作减号。逗号是典型的列表分隔符,但与下划线和连字符不同,由http_build_query进行百分比编码,并且几乎在任何地方都具有特殊含义。类似的情况是垂直条(“管道”)。

大型数组

在URL中使用大型数组时,您应该首先停止编写开始思考。这几乎总是表明糟糕的设计。 POST HTTP方法不会更合适吗?您是否有更多可读和节省空间的方法来识别所寻址的资源?

理想情况下,URL应该易于理解并且(至少部分地)记住。在里面放置一个大blob真是个坏主意。

现在我警告过你。如果您仍需要在URL中嵌入大型数组,请继续。尽可能地压缩数据base64 - 对它们进行编码以将二进制blob转换为文本,并对文本进行url-encode以对其进行清理以嵌入URL中。

修改后的base64

嗯。或者更好地使用modified version of base64。我选择的是

  • -代替+
  • _代替/
  • 省略了填充=
define('URL_BASE64_FROM', '+/');
define('URL_BASE64_TO', '-_');
function url_base64_encode($data) {
    $encoded = base64_encode($data);
    if ($encoded === false) {
        return false;
    }
    return str_replace('=', '', strtr($encoded, URL_BASE64_FROM, URL_BASE64_TO));
}
function url_base64_decode($data) {
    $len = strlen($data);
    if (is_null($len)) {
        return false;
    }
    $padded = str_pad($data, 4 - $len % 4, '=', STR_PAD_RIGHT);
    return base64_decode(strtr($padded, URL_BASE64_TO, URL_BASE64_FROM));
}

这会在每个字符上保存两个字节,否则将进行百分比编码。也无需调用urlencode函数。

压缩

应该在gzip(gzcompress)和bzip2(bzcompress)之间做出选择。不想在比较中投入时间,对于任何块大小的设置,gzip在几个相对较小的输入(大约100个字符)上看起来更好。

包装

但是应该将哪些数据输入压缩算法?

在C中,可以将整数数组转换为字符数组(字节)并将其交给压缩函数。这是最明显的做事方式。在PHP中,最明显的做法是将所有整数转换为十进制表示为字符串,然后使用分隔符进行连接,并且仅在压缩之后进行连接。多么浪费空间!

所以,让我们使用C方法!我们将摆脱分隔符和其他方式浪费空间,并使用pack编码2个字节的每个整数:

define('PACK_NUMS_FORMAT', 'n*');
function pack_nums($num_arr) {
    array_unshift($num_arr, PACK_NUMS_FORMAT);
    return call_user_func_array('pack', $num_arr);
}
function unpack_nums($packed_arr) {
    return unpack(PACK_NUMS_FORMAT, $packed_arr);
}

警告:在这种情况下,packunpack行为与机器有关。字节顺序可以在机器之间改变。但我认为这在实践中不会成为问题,因为应用程序不会同时在具有不同字节序的两个系统上运行。但是,在集成多个系统时,可能会出现问题。此外,如果您切换到具有不同字节顺序的系统,使用原始链接的链接将会中断。

一起编码

现在打包,压缩和修改base64,一体化:

function url_embed_array($arr) {
    return url_base64_encode(gzcompress(pack_nums($arr)));
}
function url_parse_array($data) {
    return unpack_nums(gzuncompress(url_base64_decode($data)));
}

请参阅result on IdeOne。它优于OP的答案,在他的40元素数组中,我的解决方案产生了91个字符,而他的一个字符为98.使用range(1, 1000)(生成array(1, 2, 3, …, 1000))作为基准时,OP’s solution produces 2712 characters while mine just 2032 characters。这大约好了25%。

为了完整起见,OP的解决方案是

function url_embed_array($arr) {
    return urlencode(base64_encode(gzcompress(implode(',', $arr))));
}

答案 1 :(得分:1)

可能有多种方法:

  1. serialize + base64 - 可以吞下任何对象,但数据开销很可怕。
  2. implode + base64 - 仅限于数组,强制用户查找未使用的char作为分隔符,数据开销要小得多。
  3. implode - 对未转义的字符串不安全。需要严格的数据控制。
  4. $foo = array('some unsafe data', '&&&==http://', '65535');
    $ser = base64_encode(serialize($foo));
    $imp = implode($foo, '|');
    $imp2 = base64_encode($imp);
    echo "$ser\n$imp\n$imp2";
    

    结果如下:

    YTozOntpOjA7czoxNjoic29tZSB1bnNhZmUgZGF0YSI7aToxO3M6MTI6IiYmJj09aHR0cDovLyI7aToyO3M6NToiNjU1MzUiO30=
    some unsafe data|&&&==http://|65535
    c29tZSB1bnNhZmUgZGF0YXwmJiY9PWh0dHA6Ly98NjU1MzU=
    

    虽然序列化+ base64结果非常长,但是implode + serialize为GET提供了可管理长度的输出......除了那个=之外。

答案 2 :(得分:0)

我认为答案取决于查询字符串的大小。

短查询字符串

对于较短的查询字符串,这可能是最好的方法:

$fs = array(5, 12, 99);
$fs_no_array = implode(',', $fs);
$url = "http://$_SERVER[HTTP_HOST]/?" .
    http_build_query(array('c' => 'asdf', 's' => 'jkl')) . '&fs=' . $fs_no_array;

导致

http://example.com/?c=asdf&s=jkl&fs=5,12,99

另一方面,你这样做是为了让你的阵列回来:

$fs = array_map('intval', explode(',', $_GET['fs']));

关于分隔符的快速说明:避免使用逗号的正当理由是它们在许多其他应用程序中用作分隔符。例如,在可能的情况下,您可能希望在Excel中解析URL,逗号可能会使其稍微困难一些。下划线也可以工作,但可以与链接的Web格式标准下划线混合使用。因此破折号实际上可能是比逗号或下划线更好的选择。

长查询字符串

我遇到了another possible solution

$fs_compressed = urlencode(base64_encode(gzcompress($fs_no_array)));

另一方面,它可以通过

解压缩
$fs_decompressed = gzuncompress(base64_decode($_GET['fs']));
$fs = array_map('intval', explode(',', $fs_decompressed));

假设它是通过GET变量传递的。

有效性测试

31个元素

$fs = array(7,2,3,4,5,6,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,52,53,54,61);

结果:

eJwFwckBwCAQxLCG%2FMh4D6D%2FxiIdpGiG5fLIR0IkRZoMWXLIJQ8%2FDIqFjYOLBy8jU0yz%2BQGlbxAB

$fs_no_array长度为84个字符,$fs_compressed长度为84个字符。同样的!

40个元素

$fs = array(7,2,3,4,5,6,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,52,53,54,61);

结果:

eJwNzEkBwDAQAzFC84jtPRL%2BxFoB0GJC0QyXhw4SMgoq1GjQoosePljYOLhw48GLL37kEJE%2FDCnSZMjSpkMXow%2BdIBUs

$fs_no_array长度为111个字符,$fs_compressed长度为98个字符。

摘要

节省的费用仅为10%左右。但是,在更大程度上,节省的费用将增加到50%以上。

如果你使用雅虎网站,你会注意到逗号分隔列表以及有时候一系列随机字符。他们可能已经在野外使用这些解决方案。

另请查看this stack question,其中详细介绍了URI中允许的内容。