Как проверить циклические ссылки в PHP при рекурсивном разборе ассоциативного массива?


Я создал этот массив с круговой ссылкой:

$arr = array(1 => 'one', 2 => 'two');
$arr[3] = &$arr;

У меня есть функция, которая рекурсивно выводит значения в массиве, но я действительно не смог решить проблему создания циклической проверки ссылок. Как ты можешь это делать?

Текущая функция, которую я использую для печати массива, скопирована ниже. Я не включил различные попытки, которые я сделал при выполнении круговой проверки ссылок. Они в основном вращались вокруг стратегии поддержания массива $seen элементов, которые имеют уже были напечатаны для каждой ветви рекурсии . Это связано с тем, что я все еще хочу разрешить печать повторяющихся значений, но не печатать значение, если оно является родительским для текущего массива, который анализируется.

Проблемы, которые у меня были, состояли в том, чтобы выяснить, как добавить ссылки, а не копии массива к этой переменной $seen. Но я был бы счастлив использовать другую стратегию все вместе, если бы это сработало.

function HTMLStringify($arr)
{

    if(is_array($arr)){
        $html = '<ul>';
        foreach ($arr as $key => $value) {

            $html .= '<li>' . $key;

            if(is_array($value)){

                //Conspicuously missing is a circular reference check,
                //causing infinite recursion. After a few failed attempts
                //at checking for this (e.g. discovering that array_push doesn't take references)
                //I have left it for further study.
                //(After all, Javascript's JSON.stringify() doesn't check for circular references)
                //TODO: Check for circular references

                $html .= HTMLStringify($value, $seen);
            }
            elseif(is_numeric($value) || is_string($value) || is_null($value))
            {
                $html .= ' = ' . $value;
            }
            else
            {
                $html .= ' [couldn't parse ' . gettype($value) . ']';
            }

            $html .= '</li>';

        }
        $html .= '</ul>';
        return $html;
    }
    else
    {
        return null;
    }
}
2 4

2 ответа:

Адаптированная версия вашего кода, использующая строгую проверку in_array из ответа, связанного Райаном Винсентом, показана ниже:

function HTMLStringify($arr, array $seen = array()) {
    if (is_array($arr)) {
        $seen[] = $arr;
        $html = '<ul>';

        foreach ($arr as $key => $value) {
            $html .= '<li>' . $key;

            if (is_array($value)) {

                if (in_array($value, $seen, true)) {
                    // Deal with recursion in your own way here
                    $html .= ' [RECURSION]';
                } else {
                    $html .= HTMLStringify($value, $seen);
                }

            } elseif (is_numeric($value) || is_string($value) || is_null($value)) {
                $html .= ' = ' . $value;
            } else {
                $html .= ' [couldn\'t parse ' . gettype($value) . ']';
            }

            $html .= '</li>'; 
        }

        return $html . '</ul>';
    } else {
        return null;
    }
}

$arr = array(1 => 'one', 2 => 'two');
$arr[3] = &$arr;
echo HTMLStringify($arr);

Сравнивая несколько версий PHP , похоже, что это будет работать для PHP 5.3.15+ и PHP 5.4.5+.

Я использую эту функцию для отладки. Также обновлено для обнаружения рекурсивной связи.

function print_table($mixed, $level=9, $_callstack=array()){
  if($level<=0){ echo '**LIMIT**'; return; }  

  if( array_search(serialize($mixed), $_callstack)!==false){
      echo '***recursive detected***';
      return ;
  }    
  $_callstack[] = serialize($mixed);

  if(is_array($mixed)){
    echo '<table cellspacing="0" width="100%" border="1">';
    foreach($mixed as $key=>$val){
      echo '<tr><td width="20%">'.$key.'</td><td>';
      if(is_array($val)){
        print_table($val,$level-1, $_callstack);
      }elseif(is_null($val)){
        echo '<span style="color:blue">null</span>';
      }elseif($val===false){
        echo '<span style="color:red">false</span>';
      }elseif($val===true){
        echo '<span style="color:green">true</span>';
      }elseif(is_numeric($val) && $val>1000000000){
        echo $val,' <span style="color:gray">[',date('d-m-Y H:i:s',$val),']</span>';
      }elseif($val===''){
        echo '<span style="color:blue">empty string</span>';      
      }else{
        echo $val;
      }
      echo '</td></tr>';
    }
    echo '</table>';
  }else{
    var_dump($mixed);
  }
}
Как вы видите, я собираю сериализованный объект, а затем сравниваю его. Сериализация требуется, потому что просто сравнение рекурсивного объекта выбрасывает фатальную ошибку:
$arr=array(&$arr);
$arr==$arr; // Fatal error: Nesting level too deep - recursive dependency? 
// php 5.2.9

Но сериализация поддерживает рекурсивные объекты! Итак, мы должны сравнить сериализованные строки,но сериализация может занять много времени и памяти. Если вы найдете другой способ-дайте мне знать:)