Преобразовать дамп var массива обратно в переменную массива


Я никогда по-настоящему не задумывался об этом до сегодняшнего дня, но после поиска в Интернете я действительно ничего не нашел. Может быть, я не совсем правильно выразился при поиске.

Заданный массив (с несколькими измерениями или без):

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

Когда var_dumped:

array(2) { ["this"]=> array(1) { ["is"]=> string(3) "the" } ["challenge"]=> array(1) { ["for"]=> array(1) { [0]=> string(3) "you" } } }

Проблема заключается в следующем: каков наилучший оптимизированный метод для перекомпиляции массива в пригодный для использования массив для PHP? Как функция undump_var(). Все ли данные находятся в одной строке в качестве вывода в браузере или содержат линия обрывается при выходе на терминал.

Это просто вопрос регулярного выражения? Или есть какой-то другой способ? Я ищу творчества.

Обновление: Примечание. Я знаком с сериализацией и несериализацией людей. Я не ищу альтернативных решений. Это вызов кода, чтобы увидеть, можно ли это сделать оптимизированным и творческим способом. Так что serialize и var_export здесь не являются решениями. И это не самые лучшие ответы.

7 38

7 ответов:

var_export или serialize это то, что ты ищешь. var_export отобразит синтаксис разбираемого массива PHP, а serialize-нечеловеческое, но обратимое преобразование "массив в строку"...

Edit хорошо, для вызова:

В основном, я преобразую выходные данные в сериализованную строку (а затем несериализую ее). Я не утверждаю, что это идеально, но, похоже, это работает на некоторых довольно сложных структурах, которые я пробовал...

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);
}

I испытал его на сложной структуре, такой как:

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
  }
}

Нет другого способа, кроме ручного разбора в зависимости от типа. Я не добавил поддержку объектов, но она очень похожа на массивы; вам просто нужно сделать некоторую магию отражения, чтобы заполнить не только открытые свойства и не запускать конструктор.

EDIT: добавлена поддержка объектов... Магия отражения...

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, в основном просто длины строк ключевых слов и некоторые скобки и т.д.)

Если вы хотите закодировать/декодировать массив, подобный этому, вы должны либо использовать var_export(), который генерирует выходные данные в массиве PHP для, например:

array(
  1 => 'foo',
  2 => 'bar'
)

Может быть результатом этого. Однако вам придется использовать eval(), чтобы получить массив обратно, и это потенциально опасный способ (тем более, что eval() действительно выполняет PHP-код, поэтому простая инъекция кода может сделать хакеров способными получить контроль над вашим PHP-скриптом).

Некоторые даже лучшие решения являются serialize(), который создает сериализованную версию любого массива или объекта; и json_encode(), который кодирует любой массив или объект в формате JSON (что более предпочтительно для обмена данными между различными языками).

Хитрость заключается в том, чтобы сопоставлять куски кода и "strings", а на строках ничего не делать, но в противном случае делать замены:

$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 не экранирует содержимое строки, нет никакого способа решить это надежно. (вы могли бы соответствовать \["[^"]*"\] но строка также может содержать "])

Используйте регулярное выражение для изменения массива (.) { (.*) } в массив($1) и eval код, это не так просто, как написано, потому что вам придется иметь дело с соответствующими кронштейнами и т. д., просто подсказка о том, как найти решение ;)

  • это будет полезно, если вы не можете изменить var_dump на var_export или сериализовать

Я думаю, что вы ищете serialize функция:

Serialize-создает хранимый объект представление значения

Это позволяет сохранить содержимое массива в удобочитаемом формате, а затем вы можете прочитать массив обратно с помощью unserialize функция.

Используя эти функции, вы можете хранить/извлекать массивы даже в текстовых / плоских файлах, а также в базе данных.

Обновлено, чтобы не использовать create_function, так как он устарел с PHP 7.2.0. Вместо этого он заменен на использование анонимных функций:



    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 = 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));