将数组的 var_dump 转换回数组变量

48

直到今天我从未真正考虑过这个问题,但在搜索网络后,我并没有找到什么。也许我的搜索词不够准确。

给定一个数组(可能是多维的或者不是):

$data = array('this' => array('is' => 'the'), 'challenge' => array('for' => array('you')));

当使用 var_dump() 函数时:

array(2) { ["this"]=> array(1) { ["is"]=> string(3) "the" } ["challenge"]=> array(1) { ["for"]=> array(1) { [0]=> string(3) "you" } } }
挑战在于:重新编译数组以供PHP使用的最佳优化方法是什么?类似于undump_var()函数。无论数据是作为浏览器输出的一行还是包含作为终端输出的换行符。
这只是一个正则表达式问题吗?还是有其他方法?我正在寻找创造性的解决方案。
更新:注意。我熟悉序列化和反序列化。我不寻找替代解决方案。这是一个代码挑战,要看是否可以用最优化和创新的方式完成。因此,序列化和var_export不是这里的解决方案。它们也不是最好的答案。

3
可以通过解析实现,但如果你真的需要这样做,那通常意味着你正在做一些错误的事情,不值得费力去做。也许可以把它变成一个社区维基代码高尔夫问题,这样就有意义了。 - deceze
肯定是可以的,但是这不是一件轻松的事情,因为语法并不是为机器解析而设计的。当你遇到像 string(8) "Foo"bar 这样的奇怪边缘情况时,它会相对比较困难,并且难以实现一个可靠的系统…如果有优雅的解决方案,我很愿意看到它们。但请意识到,大多数完整工作的解决方案可能会相对冗长,并且在内部具有相当多的逻辑... - ircmaxell
var_export() 函数有什么问题? - NullUserException
我已经将另一个问题合并到这里,供您参考。 - Andrew Barber
我认为这可以帮助你: https://dev59.com/MG855IYBdhLWcg3wbzxl - Mamoon Rashid
显示剩余2条评论
7个回答

74

您需要的是 var_export 或者 serialize 函数。 var_export 将会将数组转为可被 PHP 解析的语法形式,而 serialize 则会将其转为不易读但可逆转的字符串形式...

编辑 好吧,就挑战而言:

基本上,我将输出转换为序列化的字符串(然后再反序列化它)。虽然我不保证这是完美的,但我已经尝试过一些相当复杂的结构,似乎都可以正常工作...

function unvar_dump($str) {
    if (strpos($str, "\n") === false) {
        //Add new lines:
        $regex = array(
            '#(\\[.*?\\]=>)#',
            '#(string\\(|int\\(|float\\(|array\\(|NULL|object\\(|})#',
        );
        $str = preg_replace($regex, "\n\\1", $str);
        $str = trim($str);
    }
    $regex = array(
        '#^\\040*NULL\\040*$#m',
        '#^\\s*array\\((.*?)\\)\\s*{\\s*$#m',
        '#^\\s*string\\((.*?)\\)\\s*(.*?)$#m',
        '#^\\s*int\\((.*?)\\)\\s*$#m',
        '#^\\s*bool\\(true\\)\\s*$#m',
        '#^\\s*bool\\(false\\)\\s*$#m',
        '#^\\s*float\\((.*?)\\)\\s*$#m',
        '#^\\s*\[(\\d+)\\]\\s*=>\\s*$#m',
        '#\\s*?\\r?\\n\\s*#m',
    );
    $replace = array(
        'N',
        'a:\\1:{',
        's:\\1:\\2',
        'i:\\1',
        'b:1',
        'b:0',
        'd:\\1',
        'i:\\1',
        ';'
    );
    $serialized = preg_replace($regex, $replace, $str);
    $func = create_function(
        '$match', 
        'return "s:".strlen($match[1]).":\\"".$match[1]."\\"";'
    );
    $serialized = preg_replace_callback(
        '#\\s*\\["(.*?)"\\]\\s*=>#', 
        $func,
        $serialized
    );
    $func = create_function(
        '$match', 
        'return "O:".strlen($match[1]).":\\"".$match[1]."\\":".$match[2].":{";'
    );
    $serialized = preg_replace_callback(
        '#object\\((.*?)\\).*?\\((\\d+)\\)\\s*{\\s*;#', 
        $func, 
        $serialized
    );
    $serialized = preg_replace(
        array('#};#', '#{;#'), 
        array('}', '{'), 
        $serialized
    );

    return unserialize($serialized);
}

我对一个复杂的结构进行了测试,例如:

array(4) {
  ["foo"]=>
  string(8) "Foo"bar""
  [0]=>
  int(4)
  [5]=>
  float(43.2)
  ["af"]=>
  array(3) {
    [0]=>
    string(3) "123"
    [1]=>
    object(stdClass)#2 (2) {
      ["bar"]=>
      string(4) "bart"
      ["foo"]=>
      array(1) {
        [0]=>
        string(2) "re"
      }
    }
    [2]=>
    NULL
  }
}

1
@Gordon 你比我快了。我正要回去编辑那些链接。谢谢! - ircmaxell
2
我认为你误解了问题。挑战是将var_dump反转为数组。我熟悉serialize()和unserialize()...是远比较好的选择。这是一个代码挑战。也许这不值得努力,但我想看看是否可以以优化和创造性的方式完成。我不寻求替代方案。 - Chuck Burgess
2
@cdburgess:所以你的问题标题应该是代码挑战-将var_dump转换回数组/变量 - Sarfraz
1
你好,你的方法在Flickr上无法正常工作,var_dump数组。 警告:strpos()期望参数1为字符串,但给出了数组,在/opt/lampp/htdocs/phpflickr/example.php的第28行。注意:在/opt/lampp/htdocs/phpflickr/example.php的第59行,将数组转换为字符串。警告:unserialize()期望参数1为字符串,但给出了数组,在/opt/lampp/htdocs/phpflickr/example.php的第84行。 - Rahul Mandaliya
自 PHP 7.2.4 起不兼容。 - Black
显示剩余12条评论

16

除非根据类型进行手动解析,否则没有其他方法。

我没有为对象添加支持,但它与数组非常相似;您只需要进行一些反射魔术来填充不仅是公共属性,而且不触发构造函数即可。

编辑:已添加对象支持...反射魔法...

function unserializeDump($str, &$i = 0) {
    $strtok = substr($str, $i);
    switch ($type = strtok($strtok, "(")) { // get type, before first parenthesis
         case "bool":
             return strtok(")") === "true"?(bool) $i += 10:!$i += 11;
         case "int":
             $int = (int)substr($str, $i + 4);
             $i += strlen($int) + 5;
             return $int;
         case "string":
             $i += 11 + ($len = (int)substr($str, $i + 7)) + strlen($len);
             return substr($str, $i - $len - 1, $len);
         case "float":
             return (float)($float = strtok(")")) + !$i += strlen($float) + 7;
         case "NULL":
             return NULL;
         case "array":
             $array = array();
             $len = (int)substr($str, $i + 6);
             $i = strpos($str, "\n", $i) - 1;
             for ($entries = 0; $entries < $len; $entries++) {
                 $i = strpos($str, "\n", $i);
                 $indent = -1 - (int)$i + $i = strpos($str, "[", $i);
                 // get key int/string
                 if ($str[$i + 1] == '"') {
                     // use longest possible sequence to avoid key and dump structure collisions
                     $key = substr($str, $i + 2, - 2 - $i + $i = strpos($str, "\"]=>\n  ", $i));
                 } else {
                     $key = (int)substr($str, $i + 1);
                     $i += strlen($key);
                 }
                 $i += $indent + 5; // jump line
                 $array[$key] = unserializeDump($str, $i);
             }
             $i = strpos($str, "}", $i) + 1;
             return $array;
         case "object":
             $reflection = new ReflectionClass(strtok(")"));
             $object = $reflection->newInstanceWithoutConstructor();
             $len = !strtok("(") + strtok(")");
             $i = strpos($str, "\n", $i) - 1;
             for ($entries = 0; $entries < $len; $entries++) {
                 $i = strpos($str, "\n", $i);
                 $indent = -1 - (int)$i + $i = strpos($str, "[", $i);
                 // use longest possible sequence to avoid key and dump structure collisions
                 $key = substr($str, $i + 2, - 2 - $i + $i = min(strpos($str, "\"]=>\n  ", $i)?:INF, strpos($str, "\":protected]=>\n  ", $i)?:INF, $priv = strpos($str, "\":\"", $i)?:INF));
                 if ($priv == $i) {
                     $ref = new ReflectionClass(substr($str, $i + 3, - 3 - $i + $i = strpos($str, "\":private]=>\n  ", $i)));
                     $i += $indent + 13; // jump line
                 } else {
                     $i += $indent + ($str[$i+1] == ":"?15:5); // jump line
                     $ref = $reflection;
                 }
                 $prop = $ref->getProperty($key);
                 $prop->setAccessible(true);
                 $prop->setValue($object, unserializeDump($str, $i));
             }
             $i = strpos($str, "}", $i) + 1;
             return $object;

    }
    throw new Exception("Type not recognized...: $type");
}

在递增字符串位置计数器$i时,有很多“魔术”数字,主要是关键字的字符串长度和一些括号等。


谢谢!我喜欢你的方法,但有些字符串没有正确解析,例如:'string(6) "ab};cd"' 返回 d" - gog
@georg 噢,那是一个愚蠢的错误,我在错误的地方写了一个多余的 strlen()。现在好了吗?——我之前没有注意到它,因为我总是用长度为1的字符串进行测试... - bwoebi
@bwoebi 看起来 bool(true) 没有被正确解析。我在我的编辑中已经包含了一个修复方案。 - user1460043
@user1460043 是的,我看到了,但你的修复并不完全正确...解决方案只是不再将变量传递给strtok()。 - bwoebi
@bwoebi 是的,bool 的情况看起来更清晰且有效。但是 float 的情况似乎有问题了,请尝试使用 float(1.5) - user1460043
显示剩余2条评论

6
如果您想对这样的数组进行编码/解码,您应该使用var_export(),它可以生成PHP数组格式的输出,例如:
array(
  1 => 'foo',
  2 => 'bar'
)

可能是这个函数的结果。不过,你必须使用eval()才能获取数组,这是一种潜在的危险方式(特别是因为eval()会真正执行PHP代码,所以一个简单的代码注入就可以让黑客控制你的PHP脚本)。
甚至更好的解决方案是serialize(),它可以创建任何数组或对象的序列化版本;以及json_encode(),它可以将任何数组或对象编码为JSON格式(这是更适用于不同语言之间数据交换的格式)。

5
技巧在于按代码块和“字符串”进行匹配,对于字符串不做任何处理,而对于其他情况则进行替换:
$out = preg_replace_callback('/"[^"]*"|[^"]+/','repl',$in);

function repl($m)
{
    return $m[0][0]=='"'?
        str_replace('"',"'",$m[0])
    :
        str_replace("(,","(",
            preg_replace("/(int\((\d+)\)|\s*|(string|)\(\d+\))/","\\2",
                strtr($m[0],"{}[]","(), ")
            )
        );
}

输出:

array('this'=>array('is'=>'the'),'challenge'=>array('for'=>array(0=>'you')))

(删除从0开始的升序数字键需要进行一些额外的记账,可以在repl函数中完成。)

注意:这并不能解决包含"的字符串的问题,但似乎var_dump不会转义字符串内容,因此没有可靠的解决方法。(您可以匹配\["[^"]*"\],但一个字符串也可能包含"]


这太棒了!你是少数几个真正阅读和理解问题的人之一。感谢你接受挑战并提供一个可行的解决方案。那么如果值是 INT(5) 呢?(即 array('you',2))它将显示为 int(5),但应该从您的函数返回为 5。 - Chuck Burgess
我刚刚采用了你的示例来使它工作。用数字替换'int(\d+)'似乎并不是什么大挑战。请查看更新后的答案。 - mvds
太棒了!非常出色,代码也很小而且优化了!顺便提一下:在“\2”后面缺少一个逗号。 - Chuck Burgess

1
使用正则表达式将 array(.) { (.*) } 转换为 array($1),并对代码进行评估,这并不像所写的那样容易,因为您必须处理匹配括号等问题,以下是解决方案的提示 ;)
  • 如果您无法将 var_dump 更改为 var_export 或 serialize,则此方法将非常有用

一个正则表达式的解决方案将会非常困难,因为您可能有嵌套的大括号... 因此,更有可能涉及字符串解析器而不是正则表达式(考虑到由于嵌套而需要关注状态)... - ircmaxell
你不必处理字符串解析,正则表达式有一些出色的函数,如非贪婪/全局标志等,只需使用正确设置的标志一个单一的正则表达式即可完成 :) - canni
BBcode解析器是基于正则表达式构建的,而且不需要状态机就能很好地工作;只需将'array(.) {'和'}'视为闭合/开放标签即可 :) - canni
1
请展示一个正则表达式,将所有有效的var_dumped数据转换回本机可解析的php...如果您能展示一个可以处理以下内容的正则表达式,我会承认我错了:array(1) { ["foo}[bar]"] => string(4) "baz{" } - ircmaxell
你可能是对的,无法仅通过一个正则表达式完成,但是您可以针对每个“标签”使用一个正则表达式,其中标签是以下之一:array(。);string(。);integer(。)等等。 并且按正确顺序解析输出(简单类型->数组) 但是仍然不可能“重新解析”var_dumped对象和其他非标准结构,因此我们需要使用serialize和其他工具。 - canni
请注意,cdburgess正在寻找编程挑战,因此我将提供一些线索,说明如何实现它 :) - canni

0

已更新,不再使用create_function,因为它在PHP 7.2.0中已被弃用。取而代之的是使用匿名函数:



    function unvar_dump($str) {
        if (strpos($str, "\n") === false) {
            //添加新行:
            $regex = array(
                '#(\[.*?\]=>)#',
                '#(string\(|int\(|float\(|array\(|NULL|object\(|})#',
            );
            $str = preg_replace($regex, "\n\1", $str);
            $str = trim($str);
        }
        $regex = array(
            '#^\040*NULL\040*$#m',
            '#^\s*array\((.*?)\)\s*{\s*$#m',
            '#^\s*string\((.*?)\)\s*(.*?)$#m',
            '#^\s*int\((.*?)\)\s*$#m',
            '#^\s*bool\(true\)\s*$#m',
            '#^\s*bool\(false\)\s*$#m',
            '#^\s*float\((.*?)\)\s*$#m',
            '#^\s*\[(\d+)\]\s*=>\s*$#m',
            '#\s*?\r?\n\s*#m',
        );
        $replace = array(
            'N',
            'a:\1:{',
            's:\1:\2',
            'i:\1',
            'b:1',
            'b:0',
            'd:\1',
            'i:\1',
            ';'
        );
        $serialized = preg_replace($regex, $replace, $str);
        $func = function($match) {
            return 's:'.strlen($match[1]).':"'.$match[1].'"';
        };
        $serialized = preg_replace_callback(
            '#\s*\["(.*?)"\]\s*=>#', 
            $func,
            $serialized
        );
        $func = function($match) {
            return 'O:'.strlen($match[1]).':"'.$match[1].'":'.$match[2].':{';
        };
        $serialized = preg_replace_callback(
            '#object\((.*?)\).*?\((\d+)\)\s*{\s*;#', 
            $func, 
            $serialized
        );
        $serialized = preg_replace(
            array('#};#', '#{;#'), 
            array('}', '{'), 
            $serialized
        );
return unserialize($serialized); }
$test = 'array(10) { ["status"]=> string(1) "1" ["transactionID"]=> string(14) "1532xxx" ["orderID"]=> string(10) "1532xxx" ["value"]=> string(8) "0.73xxx" ["address"]=> string(1) "-" ["confirmations"]=> string(3) "999" ["transaction_hash"]=> string(64) "internxxx" ["notes"]=> string(0) "" ["txCost"]=> string(1) "0" ["txTimestamp"]=> string(10) "1532078165" }'; var_export(unvar_dump($test));

0

我想你正在寻找serialize函数:

serialize - 生成值的可存储表示形式

它允许您以可读格式保存数组的内容,稍后可以使用unserialize函数读取数组。

使用这些函数,您甚至可以将数组存储/检索到文本/平面文件以及数据库中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接