2012-02-02 27 views
15

Estoy tratando de implementar mi propia función de serialización/var_dump style en PHP. Parece imposible si existe la posibilidad de matrices circulares (que hay).¿Hay alguna forma de detectar matrices circulares en PHP puro?

En las últimas versiones de PHP, var_dump parece detectar matrices circulares:

php > $a = array(); 
php > $a[] = &$a; 
php > var_dump($a); 
array(1) { 
    [0]=> 
    &array(1) { 
    [0]=> 
    *RECURSION* 
    } 
} 

¿Cómo iba a aplicar mi propio tipo de serialización del método en PHP que puede detectar de manera similar? No puedo simplemente hacer un seguimiento de las matrices que he visitado, porque la comparación estricta de matrices en PHP devuelve verdadero para diferentes matrices que contienen los mismos elementos y la comparación de matrices circulares causa un error fatal, de todos modos.

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 

He buscado una forma de encontrar una identificación única (puntero) para una matriz, pero no puedo encontrar una. spl_object_hash solo funciona en objetos, no en matrices. Si lanzo múltiples diferentes matrices a objetos, todas obtienen el mismo valor spl_object_hash (¿por qué?).

EDIT:

Calling print_r, var_dump, o serializar en cada matriz y a continuación, utilizando algún mecanismo para detectar la presencia de la recursión como se detecta por estos métodos es una pesadilla complejidad algorítmica y, básicamente, hacer cualquier uso demasiado lento para ser práctico en grandes matrices anidadas.

respuesta aceptada:

que aceptó la respuesta a continuación que fue el primero en sugerir temporalmente alterar la una matriz para ver si es de hecho la misma que otra matriz. Eso responde al "¿cómo puedo comparar dos matrices para la identidad?" de la cual la detección de recursión es trivial.

+2

La respuesta va a ser: no puedes. Ver [verificar si object/array es una referencia] (http://stackoverflow.com/questions/3148125/php-check-if-object-array-is-a-reference). No es posible realizar comparaciones de referencia tipo puntero, por lo que tampoco es posible detectar un ciclo. Una mejor solución en su caso podría ser lanzarla a través de una de las funciones nativas ('json_decode (json_encode())') para deshacerse de las referencias, y solo después aplicar su propia serialización. – mario

+1

Ahora, incluso PHPUnit está utilizando el método de "marcado" temporal para detectar la recursión de matrices. – postfuturist

Respuesta

4

El método isRecursiveArray (matriz) a continuación detecta las matrices circulares/recursivas. Realiza un seguimiento de qué matrices se han visitado al agregar temporalmente un elemento que contiene una referencia de objeto conocido al final de la matriz.

Si necesita ayuda para escribir el método de serialización, actualice su pregunta de tema y proporcione un formato de serialización de muestra en su pregunta.

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); 
+1

Creo que alterar temporalmente la matriz es probablemente el única manera razonable de probar si dos matrices son iguales. Esta es la primera respuesta para sugerir ese enfoque, por lo que lo aceptaré. – postfuturist

-1

No es elegante, pero resuelve tu problema (al menos si no tienes a alguien que use * RECURSION * como valor).

<?php 
$a[] = &$a; 
if(strpos(print_r($a,1),'*RECURSION*') !== FALSE) echo 1; 
+1

Simplemente no funciona si tiene la cadena '" * RECURSION * "' en cualquier lugar de su matriz ... Es cierto que es raro, pero posible. – deceze

+0

Yeap, eso es lo que quise decir cuando menciono \ * RECURSION \ * como un valor. De todos modos, puede agregar un cheque para ese caso iterando la matriz y verificando ese valor, y obtendrá un límite de iteraciones basado en cuántas líneas imprimió la salida print_r. – dvicino

+0

Bueno, eso detecta (de una manera extremadamente ineficiente) que hay recurrencia _en alguna parte_ tal vez ... realmente no resuelve mi caso de uso. – postfuturist

0

método divertido (ya sé que es estúpido :)), pero se puede modificar y seguir el "camino" al elemento recursivo. Esto es solo una idea :) Basado en la propiedad de la cadena serializada, cuando comienza la recursión será la misma que la cadena para la matriz original. Como puede ver, lo intenté con muchas variaciones diferentes y podría ser algo capaz de 'engañarlo', pero 'detecta' todas las recursiones enumeradas. Y no probé arreglos recursivos con objetos.

$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 corresponde a la situación en la que el elemento con recursividad se encuentra en el 'extremo' de la matriz. y, sí, esta recursión está relacionada con toda la matriz. Es posible hacer una recursión de los subelementos, pero ya es demasiado tarde para pensar en ellos. De alguna manera, cada elemento de la matriz debe verificarse para la recursividad en sus subelementos. De la misma manera, como en el caso anterior, a través del resultado de la función print_r, o buscando un registro específico para la recursión en una cadena serializada (R:4;} algo así). Y el rastreo debe comenzar desde ese elemento, comparando todo lo que está debajo con mi script. Todo eso es solo si desea detectar dónde comienza la recursión, no solo si la tiene o no.

ps: pero lo mejor debería ser, como creo, escribir su propia función de deserialización de serie seriada creada por php.

0

Mi enfoque es tener una matriz temporal que contenga una copia de todos los objetos que ya fueron iterados. como esto aquí:

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

Y aquí es un código de demostración rápida para ilustrar cómo funciona:

$obj = new StdClass(); 
$obj->arr = []; 
$obj->arr[] = 'Foo'; 
$obj->arr[] = $obj; 
$obj->arr[] = 'Bar'; 
$obj->final = 12345; 
$obj->a2 = $obj->arr; 

dump($obj, 'obj'); 

Este script generará el siguiente resultado:

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 
Cuestiones relacionadas