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

But*_*kus 1 php arrays query-string

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

如果只填充一个数组($fn),PHP将在查询字符串中执行大量重复http_build_query:

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

结果$url

http://example.com/?c=asdf&fs[0]=5&fs[1]=12&fs[3]=99
Run Code Online (Sandbox Code Playgroud)

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

Pal*_*lec 5

默认的PHP方式

http_build_query将数组序列化为URL的常用方法是什么?PHP会自动将其反序列化$_GET.

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

小阵列

对于小型阵列,转换为下划线列表非常方便有效.它完成了$fs = implode('_', $fs).然后你的URL看起来像这样:

http://example.com/?c=asdf&fs=5_12_99
Run Code Online (Sandbox Code Playgroud)

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

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

大型阵列

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

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

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

修改base64

嗯.或者更好地使用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));
}
Run Code Online (Sandbox Code Playgroud)

这会在每个字符上保存两个字节,否则将进行百分比编码.也没有必要调用urlencode函数.

压缩

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

填料

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

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

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

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);
}
Run Code Online (Sandbox Code Playgroud)

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

编码在一起

现在打包,压缩和修改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)));
}
Run Code Online (Sandbox Code Playgroud)

在IdeOne上查看结果.它优于OP的答案,在他的40元素数组中,我的解决方案产生91个字符,而他的98个字符.当使用range(1, 1000)(生成array(1, 2, 3, …, 1000))作为基准时,OP的解决方案产生2712个字符,而我的只有2032个字符.这大约好25%.

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

function url_embed_array($arr) {
    return urlencode(base64_encode(gzcompress(implode(',', $arr))));
}
Run Code Online (Sandbox Code Playgroud)