Обнаружение ссылок в массиве из другой функции

Поэтому я использую метод pin, но ссылка обнаружена на одном уровне слишком поздно:

$pin = time(); function wrap($arr){ test($arr); } function test(&$arr){ global $pin; if(in_array($pin, $arr)) return print "ref"; $arr[] = $pin; foreach($arr as &$v){ if($v != $pin){ if(is_array($v)) return test($v); print $v . " "; } } } $array = array(1, 2, 3); $array[4] = &$array; wrap($array); 

Я получаю 1 2 3 1 2 3 rec

Но я ожидаю 1 2 3 rec

Если я просто выполняю test($arr) тогда он работает, но проблема в том, что мне нужно обернуть тестовую функцию внутри другой, которая принимает значения не ссылки 🙁

Есть ли способ обнаружить ссылку в нужный момент с помощью моей функции обертки?

Введение

Я думаю, что лучший подход состоял бы в том, чтобы создать копию массива и сравнить модификацию, а не использовать глобальный вывод, и он все равно может быть 100% Recursive

Пример 1

Это из вашего примера выше:

 $array = array(1,2,3); $array[4] = &$array; wrap($array); 

Вывод

 Array ( [0] => 1 [1] => 2 [2] => 3 [4] => ref ) 

Пример 2.

Действительно ли мы уверены, что его ссылка на обнаружение или просто копия массива

 //Case 1 : Expect no modification $array = array(1, 2, 3, array(1, 2, 3)); wrap( $array); //Case 2 : Expect Modification in Key 2 $array = array(1, 2, 3, array(1, 2, 3)); $array[2] = &$array; wrap( $array); 

Вывод

 Array ( [0] => 1 [1] => 2 [2] => 3 [3] => Array ( [0] => 1 [1] => 2 [2] => 3 ) ) Array ( [0] => 1 [1] => 2 [2] => ref [3] => Array ( [0] => 1 [1] => 2 [2] => 3 ) ) 

Пример 3.

Действительно ли это рекурсивно?

 $array = array(1, 2, 3, array(1, 2, 3)); $array[4][4][2][6][1] = array(1,2,3=>&$array); wrap( $array); 

Вывод

 Array ( [0] => 1 [1] => 2 [2] => 3 [3] => Array ( [0] => 1 [1] => 2 [2] => 3 ) [4] => Array ( [4] => Array ( [2] => Array ( [6] => Array ( [1] => Array ( [0] => 1 [1] => 2 [3] => ref <-- GOT YOU ) ) ) ) ) ) 

Модифицированная функция

 /** * Added printf since test now returns array * @param array $arr */ function wrap(array $arr) { printf("<pre>%s<pre>", print_r(test($arr), true)); } /** * - Removed Top Refrence * - Removed Global * - Add Recursion * - Returns array * @param array $arr * @return array */ function test(array $arr) { $temp = $arr; foreach ( $arr as $key => &$v ) { if (is_array($v)) { $temp[$key]['_PIN_'] = true; $v = isset($arr[$key]['_PIN_']) ? "ref" : test($v); } } unset($temp); // cleanup return $arr; } с /** * Added printf since test now returns array * @param array $arr */ function wrap(array $arr) { printf("<pre>%s<pre>", print_r(test($arr), true)); } /** * - Removed Top Refrence * - Removed Global * - Add Recursion * - Returns array * @param array $arr * @return array */ function test(array $arr) { $temp = $arr; foreach ( $arr as $key => &$v ) { if (is_array($v)) { $temp[$key]['_PIN_'] = true; $v = isset($arr[$key]['_PIN_']) ? "ref" : test($v); } } unset($temp); // cleanup return $arr; } 

Я думаю, вы слишком усложняете ситуацию. Я решил это, перейдя по массиву и проверив, соответствует ли текущее значение в массиве ( === ) массивом.

 function wrap( $arr){ test($arr); } function test( $arr){ foreach( $arr as $v) { if( $v === $arr) { print 'ref, '; } else { if( is_array( $v)) { test( $v); } else { print $v . ', '; } } } } 

Я использовал следующие тестовые примеры:

 echo "Array 1:\n"; $array1 = array(1, 2, 3); $array1[4] = &$array1; wrap( $array1); echo "\nArray 2:\n"; $array2 = array(1, 2, 3, array(1, 2, 3)); $array2[2] = &$array2; wrap( $array2); 

Который произвел этот результат :

 Array 1: 1, 2, 3, ref Array 2: 1, 2, ref, 1, 2, 3, 

Однако вышеприведенный метод не будет использоваться для вложенных ссылок. Если возможны вложенные ссылки, как в следующем тестовом примере:

 echo "\nArray 3:\n"; $array3 = array(1, 2, 3, array(1, 2, 3)); $array3[3][2] = &$array3; wrap( $array3); 

Затем нам нужно отслеживать все ссылки на массивы, которые мы видели, например:

 function wrap( $arr){ test( $arr); } function test( $arr){ $refs = array(); // Array of references that we've seen $f = function( $arr) use( &$refs, &$f) { $refs[] = $arr; foreach( $arr as $v) { if( in_array( $v, $refs)) { print 'ref, '; } else { if( is_array( $v)) { $f( $v); } else { print $v . ', '; } } } }; $f( $arr); } 

Используя приведенный выше тестовый пример, выходы:

 Array 3: 1, 2, 3, 1, ref, 3, 

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

 function wrap($arr){ test($arr); } /// ... wrap($array); 

Функция wrap() выделяет новый блок памяти для $arr . Когда вы вызываете функцию test() внутри тела wrap() , она берет ссылку блока памяти $arr , но не блока памяти $array s, потому что $arr – это копия $array и система управления памятью PHP хранит их отдельно .


Существует универсальная эталонная функция определения пятен:

 function is_equal_refs(&$a, &$b){ $buffer = $a; // saving current value in temporary variable $a = md5(time()); // assigning new value to memory block, pointed by reference $result = ($a === $b); // if they're still equal, then they're point to the same place. $a = $buffer; // restoring value return $result; // returning result } 

Итак, давайте проведем некоторые тесты:

 <?php header('Content-Type: text/plain'); function is_equal_refs(&$a, &$b){ $buffer = $a; $a = md5(time()); $result = ($a === $b); $a = $buffer; return $result; } function wrap($arr){ test($arr); } function test(&$arr){ foreach($arr as &$v){ if(is_equal_refs($arr, $v)){ print_r('ref'); echo PHP_EOL; break; } if(is_array($v))return test($v); print_r($v); echo PHP_EOL; } } $array = array(1, 2, 3); $array[] = &$array; wrap($array); ?> 

Показывает:

 1 // < $arr 2 3 1 // < $array 2 3 ref // < $array doubled -> reference found 

Причиной такого поведения является $arr[3] содержит ссылку для блока памяти $array s, но не ссылку на блок памяти самого себя.

Позволяет удалить $array[] = &$array; строка и изменить функцию wrap() чтобы проверить:

 function wrap($arr){ $arr[] = &$arr; test($arr); } 

И результатом будет:

 1 // < $arr 2 3 ref // < $arr doubled -> reference found 

Потому что $arr не указывает на $array , а на себя в $arr[3] . Итак, в вашем коде есть разные ссылки, которые вы хотите увидеть.


ЗАКЛЮЧЕНИЕ: То, что вы хотите достичь, – это нарушение правил управления памятью PHP.


UPDv1:

Необходимо искать обходной путь , чтобы восстановить ссылку $array в области функции wrap() .

1) Практика « плохих » / « глобальных »:

 <?php header('Content-Type: text/plain'); function is_equal_refs(&$a, &$b){ $buffer = $a; $a = md5(time()); $result = ($a === $b); $a = $buffer; return $result; } function wrap($array){ global $check; // <- THIS test(empty($check) ? $array : $check); // <- THIS } function test(&$arr){ foreach($arr as &$v){ if(is_equal_refs($v, $arr)){ print_r('ref'); echo PHP_EOL; break; } if(is_array($v)){ test($v); } else { print $v . ' '; echo PHP_EOL; } } } $array = array(1, 2, 3); $array[] = &$array; $check = &$array; // <- and THIS wrap($array); ?> 

Что показывает:

 1 2 3 ref 

2) Практика « обернуть все в массиве или объекте »: (предпочтительный и надежный)

 <?php header('Content-Type: text/plain'); define('REF_MARKER', 'x-my-tr!cky-ref'); // trick key definition function is_equal_refs(&$a, &$b){ $buffer = $a; $a = md5(time()); $result = ($a === $b); $a = $buffer; return $result; } function wrap(array $arr){ // restore reference, if trick. // it might be moved to the top part of test() function (might affect performance). if(isset($arr[REF_MARKER]))$arr = &$arr[REF_MARKER]; test($arr); } // $array - subject to test; // $refs - internal ref list of all `subjects`; function test(&$array, $refs = array()){ $refs[] = &$array; foreach($array as &$value){ foreach($refs as &$ref){ if(is_equal_refs($ref, $value))return print 'ref '; } if(is_array($value)){ $refs[] = &$value; test($value, $refs); } else { print $value . ' '; } } } $array = array(1, 2, 3); $array[] = &$array; wrap(array(REF_MARKER => &$array)); // trick print PHP_EOL; $ring = array(1, 2, 3, array(4, 5, 6)); $ring[3][] = &$ring; wrap(array(REF_MARKER => &$ring)); // trick print PHP_EOL; $test = array('a', 'b', 'c'); $ring = array(1, 2, 3); $ring[] = &$test; $test[] = &$ring; wrap(array(REF_MARKER => &$ring)); // trick print PHP_EOL; wrap(range(1, 5)); // normal print PHP_EOL; $test = array(1, 2, 3, array(1, 2, 3), 4, array(5, 2, 3), array(6, array(1, 2, 3), 7), array(1, 2, 3)); wrap($test); // normal print PHP_EOL; $test[] = &$test; $test[3][] = &$test; $test[5][] = &$test[3]; wrap(array(REF_MARKER => &$test)); // trick ?> 

Показывает:

 1 2 3 ref 1 2 3 4 5 6 ref 1 2 3 abc ref 1 2 3 4 5 1 2 3 1 2 3 4 5 2 3 6 1 2 3 7 1 2 3 1 2 3 1 2 3 ref 4 5 2 3 ref 6 1 2 3 7 1 2 3 ref