10

So I'm using the pin method, but the reference is detected one level too late:

$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);

I get 1 2 3 1 2 3 rec

But I expect 1 2 3 rec

If I just do test($arr) then it works, but the problem is that I need to wrap the test function inside another one that accepts values not references :(

Is there any way I can detect the reference at the right moment with my wrapper function too?

asked Apr 3, 2013 at 0:28
7
  • $pin never changes, is that intentional? Commented Apr 3, 2013 at 0:35
  • yes. it's supposed to be used to compare if two arrays are the same variable.. Commented Apr 3, 2013 at 0:40
  • 1
    @Alex autodetect? I don't think so, only: function wrap(&$arr). Commented Apr 3, 2013 at 0:42
  • 1
    Short answer: no. As soon as you pass the input by value, the internal reference is no longer to the passed structure, it's to the original external one. The only way this is possible is to pass by reference, if you do that then I refer you to my earlier answer on the subject of detecting whether two given variables are references to each other. Commented Apr 25, 2013 at 9:38
  • 1
    I would scrap the whole method as rewrite it, there's so many things wrong with that :) Commented Apr 25, 2013 at 10:58

3 Answers 3

12
+500

Introduction

I think a better approach would be to create a copy of the array and compare modification rather than use global pin and it can still be a 100% Recursive

Example 1

This is from your example above :

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

Output

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

Example 2

Are we really sure its detecting reference or just a copy of the array

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

Output

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
 )
)

Example 3

Is this really recursive ?

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

Output

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
 )
 )
 )
 )
 )
)

Your Modified Function

/**
 * 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;
}
answered Apr 25, 2013 at 8:38
0
5

I think you are over-complicating things. I solved this by looping over the array and checking if the current value in the array is equivalent (===) with the array.

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 . ', ';
 }
 }
 }
}

I used the following test cases:

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

Which produced this output:

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

However, the above method will fail for nested references. If nested references are possible, as in the following test case:

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

Then we need to keep track of all the array references we've seen, like this:

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

Using the above test case, this outputs:

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

Edit: I've updated the final function that keeps track of all references to eliminate the global dependencies.

answered Apr 3, 2013 at 0:45
4
  • it's not supposed to return after the reference. it should just print 'ref' and continue to the next element... Commented Apr 3, 2013 at 1:11
  • @Alex - I've updated my answer and cleaned up the comments on this post. Please let me know if it does not perform how you expect. Commented Apr 4, 2013 at 20:41
  • your solution is still producing false-positives for arrays such as: $array = array(1, 2, array(1, 2), array(1, 2));. By the way, I saw what you did there. ;) Commented Apr 25, 2013 at 14:02
  • @Yoshi - Crap, was afraid of that. But since you made such a compelling argument against global variables, I had to refactor them out. ;) Commented Apr 25, 2013 at 14:15
5
function wrap($arr){ test($arr); }
/// ...
wrap($array);

Your wrap() function allocates new memory block for $arr. When you calling test() function within wrap()s body, it takes reference of $arr memory block, but not an $arrays memory block, because $arr is a copy of $array and PHP memory management system stores them separately.


There is a universal reference spotting function:

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
}

So, lets do some testing:

<?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);
?>

Shows:

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

The reason of such behavior is $arr[3] contains reference for $arrays memory block, but not reference of itself's memory block.

Lets remove a $array[] = &$array; row, and modify wrap() function to check:

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

And result would be:

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

Because $arr not points to $array, but to itself in $arr[3]. So, in your code there are different references you want to spot.


CONCLUSION: What you want to achieve is breaking out PHP memory management rules.


UPDv1:

Need to seek a workaround, to restore $array reference in a wrap() function scope.

1) A "bad" / "globals" practice:

<?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);
?>

Which shows:

1
2
3
ref

2) A "wrap everything in array or object" practice: (prefered and reliable)

<?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
?>

Shows:

1 2 3 ref
1 2 3 4 5 6 ref
1 2 3 a b c 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
0

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.