Reference detection in array from another function

前端 未结 3 1219
轻奢々
轻奢々 2021-01-02 05:11

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         


        
相关标签:
3条回答
  • 2021-01-02 05:21

    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.

    0 讨论(0)
  • 2021-01-02 05:27

    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;
    }
    
    0 讨论(0)
  • 2021-01-02 05:35
    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 讨论(0)
提交回复
热议问题