检测PHP中的无限数组递归?

时间:2012-01-28 01:19:57

标签: php reflection recursion detect

我刚刚在我的宠物项目dump_r()

中重写了我的递归检测算法

https://github.com/leeoniya/dump_r.php

检测对象递归并不太难 - 使用spl_object_hash()获取对象实例的唯一内部id,将其存储在dict中并在转储其他节点时与其进行比较。

对于数组递归检测,我有点困惑,我没有发现任何有用的东西。 php本身能够识别递归,虽然它似乎太晚了一个周期。 编辑:nvm,它出现在需要的地方:)

$arr = array();
$arr[] = array(&$arr);
print_r($arr);

它是否必须求助于跟踪递归堆栈中的所有内容并对每个其他数组元素进行浅层比较?

任何帮助将不胜感激,
谢谢!

2 个答案:

答案 0 :(得分:9)

由于PHP的call-by-value机制,我在这里看到的唯一解决方案是通过引用迭代数组,并在其中设置一个任意值,稍后您将检查它是否存在以查明是否存在你之前在那里:

function iterate_array(&$arr){

  if(!is_array($arr)){
    print $arr;
    return;
  }

  // if this key is present, it means you already walked this array
  if(isset($arr['__been_here'])){
    print 'RECURSION';
    return;
  }

  $arr['__been_here'] = true;

  foreach($arr as $key => &$value){

    // print your values here, or do your stuff
    if($key !== '__been_here'){
      if(is_array($value)){
        iterate_array($value);
      }

      print $value;
    }
  }

  // you need to unset it when done because you're working with a reference...
  unset($arr['__been_here']);

}

您可以将此函数包装到另一个接受值而不是引用的函数中,但是您将从第二级获得RECURSION通知。我认为print_r也是这样。

答案 1 :(得分:3)

如果我错了,有人会纠正我,但PHP实际上是在适当的时候检测递归。您的分配只会创建附加周期。例子应该是:

$arr    = array();
$arr    = array(&$arr);

这将导致

array(1) { [0]=> &array(1) { [0]=> *RECURSION* } } 

正如所料。


好吧,我对自己如何检测递归有点好奇,我开始使用谷歌。我找到了这篇文章http://noteslog.com/post/detecting-recursive-dependencies-in-php-composite-values/和这个解决方案:

function hasRecursiveDependency($value)
{
    //if PHP detects recursion in a $value, then a printed $value 
    //will contain at least one match for the pattern /\*RECURSION\*/
    $printed = print_r($value, true);
    $recursionMetaUser = preg_match_all('@\*RECURSION\*@', $printed, $matches);
    if ($recursionMetaUser == 0)
    {
        return false;
    }
    //if PHP detects recursion in a $value, then a serialized $value 
    //will contain matches for the pattern /\*RECURSION\*/ never because
    //of metadata of the serialized $value, but only because of user data
    $serialized = serialize($value);
    $recursionUser = preg_match_all('@\*RECURSION\*@', $serialized, $matches);
    //all the matches that are user data instead of metadata of the 
    //printed $value must be ignored
    $result = $recursionMetaUser > $recursionUser;
    return $result;
}