phparraysreference

Use a reference to $a to make $a a reference to $b


I have an array $arr = ['a', ['b',['c','d','e']]];.
So $arr[1][1][0] is 'c'.

I have these indexes listed in n array: $idx = [1,1,0];
(this may sound tricky, but comes from the fact that I got them from parsing a kind of scripting pseudo-language).

I have a recursive function which returns a reference to the expected element:
function &getDeepRef(&$array, $indexes){...}

Usage:
$ref = & getDeepRef($arr, $idx);

At that point, everything works as expected:
echo $ref;===> outputs 'c'
$ref = 12;
print_r($arr); ==> $arr is now ['a', ['b',[12,'d','e']]]

Here comes my question:
I need $arr[1][1][0] to become a reference to another variable $otherVar.
Can I do this using the $ref reference?

If yes, I couldn't find the right syntax for this
($ref = &$OtherVar; unsets $ref as a reference to $arr[1][1][0], and makes it a reference to $otherVar, but in no way helps make $arr[1][1][0] a reference to $otherVar).

If no, how would you make the array element which indexes are listed in an array, become a reference to a given variable?

[Edit] I found a solution based on eval();, but I'd rather use another way for security reasons:

    $arr = ['a', ['b', 'ii'=> ['c','d','e']],'f'];
    out($arr);
    
    $idxLeft  = [2];
    $idxRight = [1,'ii',0];
    
    $ref = &getDeepRef($arr,$idxRight);//$ref now is a reference to $arr[1]['ii'][0]
    $ref = 'ccc';//demo: $arr[1]['ii'][0] is now 'ccc'
    out($arr);
    
    //Now the target: $arr[2] = &$arr[1]['ii'][0];
    //Following does NOT work:
    $leftRef  = &getDeepRef($arr,$idxLeft);
    $rightRef = &getDeepRef($arr,$idxRight);
    $leftRef = &$rightRef; //this did NOT make $arr[2] a reference to $arr[1]['ii'][0]
        //instead it broke the reference from $leftRef on $arr[2] and made a new reference from $leftRef onto $arr[1]['ii'][0].
    
    //Following DOES work, but I'd prefer avoiding eval();  [notice: the mapping with json_encode is just here for string indexes compatibility, like 'ii']
    $eval = '$arr['. implode('][',array_map('json_encode',$idxLeft)) . '] = &$arr['. implode('][',array_map('json_encode',$idxRight)) . '];';
    out($eval);
    eval($eval);
    out($arr);
    
    $a = &getDeepRef($arr,$idxLeft);
    $a=45;
    out($arr);


    function &getDeepRef(&$var,$indexes){//third param just for exception message
        if(null===($id=array_shift($indexes))){ return $var; }
        if(!is_array($var) || !array_key_exists($id, $var)){ throw new \Exception("Bad key: [$id]"); }
        return getDeepRef($var[$id],$indexes);
    }
    
    function out($v){
        static $i=0;
        echo '<br>'.++$i.' - '; print_r($v);
    }

Solution

  • You do not need to set the reference of your internal array element to another variable. Doing

    $ref = &$otherVar;
    

    as you pointed out indeed loses the reference. However, you do not need to set the reference of it. Instead, you can set the other variable. Here's an example:

        $arr = ['a', ['b', 'ii'=> ['c','d','e']],'f'];
        out($arr);
    
        $idxLeft  = [2];
        $idxRight = [1,'ii',1];
    
        $a = &getDeepRef($arr,$idxRight);
        $a=45;
        out($arr);
        $b = 456;
        $a = $b;
        $b = &$a;
        $b++;
        out($arr);
    
    
        function &getDeepRef(&$var,$indexes){//third param just for exception message
            if(null===($id=array_shift($indexes))){ return $var; }
            if(!is_array($var) || !array_key_exists($id, $var)){ throw new \Exception("Bad key: [$id]"); }
            return getDeepRef($var[$id],$indexes);
        }
    
        function out($v){
            static $i=0;
            echo '<br>'.++$i.' - '; print_r($v);
        }
    

    Or in short:

    $a = $b;
    $b = &$a;
    

    Where $a is the variable whose reference you want to keep (inferred from the array) and $b is the variable you want to be linked to that array value.

    The output:

    <br>1 - Array
    (
        [0] => a
        [1] => Array
            (
                [0] => b
                [ii] => Array
                    (
                        [0] => c
                        [1] => d
                        [2] => e
                    )
    
            )
    
        [2] => f
    )
    <br>2 - Array
    (
        [0] => a
        [1] => Array
            (
                [0] => b
                [ii] => Array
                    (
                        [0] => c
                        [1] => 45
                        [2] => e
                    )
    
            )
    
        [2] => f
    )
    <br>3 - Array
    (
        [0] => a
        [1] => Array
            (
                [0] => b
                [ii] => Array
                    (
                        [0] => c
                        [1] => 457
                        [2] => e
                    )
    
            )
    
        [2] => f
    )
    

    Reference is not an attribute of a variable, but its location. Once you say you want to move the location of a variable, then you lose track of its former value. So you need to keep the reference to your array item, that's how you can change its value from "remote" and consequently, change the reference of your other variable.

    EDIT

    Here's an example where I define setDeepRef where you can entangle array elements with each-other:

        $arr = ['a', ['b', 'ii'=> ['c','d','e']],'f'];
        out($arr);
    
        $idxLeft  = [2];
        $idxRight = [1,'ii',1];
    
        $a = &getDeepRef($arr,$idxRight);
        setDeepRef($arr, $a, $idxLeft);
        $a = "something";
        out($arr);
    
    
        function &getDeepRef(&$var,$indexes){//third param just for exception message
            if(null===($id=array_shift($indexes))){ return $var; }
            if(!is_array($var) || !array_key_exists($id, $var)){ throw new \Exception("Bad key: [$id]"); }
            return getDeepRef($var[$id],$indexes);
        }
        
        function setDeepRef(&$var, &$reference, $indexes, $depth = 0) {
            if (!isset($var[$indexes[$depth]])) throw new \Exception("Bad key: [$id]");
            if ($depth < count($indexes) - 2) {
                setDeepRef($var[$indexes[$depth]], $reference, $indexes, $depth + 1);
            } else {
                $var[$indexes[$depth]] = &$reference;
            }
        }
    
        function out($v){
            static $i=0;
            echo '<br>'.++$i.' - '; print_r($v);
        }
    

    It's almost what you wanted, with one caveat:

    If you already saved the address of one of the array elements and then you change that element, then you will lose track of the variable.