Я пытаюсь реализовать свою собственную функцию стиля сериализации / var_dump в PHP. Это кажется невозможным, если есть возможность круговых массивов (что есть).
В последних версиях PHP var_dump обнаруживает круговые массивы:
php > $a = array(); php > $a[] = &$a; php > var_dump($a); array(1) { [0]=> &array(1) { [0]=> *RECURSION* } }
Как я могу реализовать свой собственный метод сериализации в PHP, который может обнаруживать аналогично? Я не могу просто отслеживать, какие массивы я посетил, потому что строгое сравнение массивов в PHP возвращает true для разных массивов, содержащих одни и те же элементы, и сравнение круговых массивов вызывает фатальную ошибку.
php > $b = array(1,2); php > $c = array(1,2); php > var_dump($b === $c); bool(true) php > $a = array(); php > $a[] = &$a; php > var_dump($a === $a); PHP Fatal error: Nesting level too deep - recursive dependency? in php shell code on line 1
Я искал способ найти уникальный идентификатор (указатель) для массива, но я не могу его найти. spl_object_hash работает только с объектами, а не с массивами. Если я передаю несколько разных массивов объектам, они получают одинаковое значение spl_object_hash (почему?).
РЕДАКТИРОВАТЬ:
Вызов print_r, var_dump или сериализация в каждом массиве, а затем использование какого-либо механизма для обнаружения присутствия рекурсии, обнаруженного этими методами, является кошмаром с алгоритмической сложностью и в основном сделает использование слишком медленным, чтобы быть практичным на больших вложенных массивах.
ПРИНЯТЫЙ ОТВЕТ:
Я принял нижеприведенный ответ, который первым предложил временно изменить массив, чтобы убедиться, что он действительно совпадает с другим массивом. Это отвечает на вопрос «как сравнить два массива для идентификации?» из которого обнаружение рекурсии тривиально.
Метод isRecursiveArray (array) ниже определяет круговые / рекурсивные массивы. Он отслеживает, какие массивы были посещены, временно добавив элемент, содержащий известную ссылку на объект, в конец массива.
Если вам нужна помощь в написании метода сериализации, обновите свой вопрос и укажите примерный формат сериализации в своем вопросе.
function removeLastElementIfSame(array & $array, $reference) { if(end($array) === $reference) { unset($array[key($array)]); } } function isRecursiveArrayIteration(array & $array, $reference) { $last_element = end($array); if($reference === $last_element) { return true; } $array[] = $reference; foreach($array as &$element) { if(is_array($element)) { if(isRecursiveArrayIteration($element, $reference)) { removeLastElementIfSame($array, $reference); return true; } } } removeLastElementIfSame($array, $reference); return false; } function isRecursiveArray(array $array) { $some_reference = new stdclass(); return isRecursiveArrayIteration($array, $some_reference); } $array = array('a','b','c'); var_dump(isRecursiveArray($array)); print_r($array); $array = array('a','b','c'); $array[] = $array; var_dump(isRecursiveArray($array)); print_r($array); $array = array('a','b','c'); $array[] = &$array; var_dump(isRecursiveArray($array)); print_r($array); $array = array('a','b','c'); $array[] = &$array; $array = array($array); var_dump(isRecursiveArray($array)); print_r($array);
сfunction removeLastElementIfSame(array & $array, $reference) { if(end($array) === $reference) { unset($array[key($array)]); } } function isRecursiveArrayIteration(array & $array, $reference) { $last_element = end($array); if($reference === $last_element) { return true; } $array[] = $reference; foreach($array as &$element) { if(is_array($element)) { if(isRecursiveArrayIteration($element, $reference)) { removeLastElementIfSame($array, $reference); return true; } } } removeLastElementIfSame($array, $reference); return false; } function isRecursiveArray(array $array) { $some_reference = new stdclass(); return isRecursiveArrayIteration($array, $some_reference); } $array = array('a','b','c'); var_dump(isRecursiveArray($array)); print_r($array); $array = array('a','b','c'); $array[] = $array; var_dump(isRecursiveArray($array)); print_r($array); $array = array('a','b','c'); $array[] = &$array; var_dump(isRecursiveArray($array)); print_r($array); $array = array('a','b','c'); $array[] = &$array; $array = array($array); var_dump(isRecursiveArray($array)); print_r($array);
Смешной метод (я знаю, что это глупо :)), но вы можете изменить его и отслеживать «путь» к рекурсивному элементу. Это просто идея 🙂 Исходя из свойства сериализованной строки, когда начинается рекурсия, она будет такой же, как строка для исходного массива. Как вы можете видеть – я пробовал это во многих разных вариантах, и может быть, что-то может «обмануть» его, но «обнаруживает» все перечисленные рекурсии. И я не пытался рекурсивные массивы с объектами.
$a = array('b1'=>'a1','b2'=>'a2','b4'=>'a3','b5'=>'R:1;}}}'); $a['a1'] = &$a; $a['b6'] = &$a; $a['b6'][] = array(1,2,&$a); $b = serialize($a); print_r($a); function WalkArrayRecursive(&$array_name, &$temp){ if (is_array($array_name)){ foreach ($array_name as $k => &$v){ if (is_array($v)){ if (strpos($temp, preg_replace('#R:\d+;\}+$#', '', serialize($v)))===0) { echo "\n Recursion detected at " . $k ."\n"; continue; } WalkArrayRecursive($v, $temp); } } } } WalkArrayRecursive($a, $b);
regexp – это ситуация, когда элемент с рекурсией находится на «конце» массива. и, да, эта рекурсия связана со всем массивом. Можно сделать рекурсию подэлементов, но мне слишком поздно думать о них. Так или иначе, каждый элемент массива должен быть проверен для рекурсии в своих подэлементах. Точно так же, как и выше, через вывод функции print_r или поиск определенной записи для рекурсии в сериализованной строке ( R:4;}
примерно так). И трассировка должна начинаться с этого элемента, сравнивая все ниже по моему сценарию. Все это только в том случае, если вы хотите определить, где начинается рекурсия, а не только, есть ли у вас это или нет.
ps: но лучше всего, как я думаю, лучше написать собственную функцию unserialize из serailized string, созданной самой php.
Мой подход состоит в том, чтобы иметь массив temp, который содержит копию всех объектов, которые уже были итерации. вот так вот:
// We use this to detect recursion. global $recursion; $recursion = []; function dump( $data, $label, $level = 0 ) { global $recursion; // Some nice output for debugging/testing... echo "\n"; echo str_repeat( " ", $level ); echo $label . " (" . gettype( $data ) . ") "; // -- start of our recursion detection logic if ( is_object( $data ) ) { foreach ( $recursion as $done ) { if ( $done === $data ) { echo "*RECURSION*"; return; } } // This is the key-line: Remember that we processed this item! $recursion[] = $data; } // -- end of recursion check if ( is_array( $data ) || is_object( $data ) ) { foreach ( (array) $data as $key => $item ) { dump( $item, $key, $level + 1 ); } } else { echo "= " . $data; } }
И вот какой-то быстрый демо-код, чтобы проиллюстрировать, как это работает:
$obj = new StdClass(); $obj->arr = []; $obj->arr[] = 'Foo'; $obj->arr[] = $obj; $obj->arr[] = 'Bar'; $obj->final = 12345; $obj->a2 = $obj->arr; dump( $obj, 'obj' );
Этот скрипт будет генерировать следующий результат:
obj (object) arr (array) 0 (string) = Foo 1 (object) *RECURSION* 2 (string) = Bar final (integer) = 12345 a2 (array) 0 (string) = Foo 1 (object) *RECURSION* 2 (string) = Bar
Это не изящно, но решает вашу проблему (по крайней мере, если у вас нет кого-то, использующего * RECURSION * в качестве значения).
<?php $a[] = &$a; if(strpos(print_r($a,1),'*RECURSION*') !== FALSE) echo 1;