Search code examples
phpreferencebinary-operators

Playing with references


I can see why

$a = new ArrayObject();
$a['ID'] = 42;
$b = &$a['ID'];
$c = $a;
$c['ID'] = 37;
echo $a['ID']."\n";
echo $b."\n";
echo $c['ID']."\n";

outputs 37, 42, 37

while

$a = new ArrayObject();
$a['ID'] = 42;
$b = &$a['ID'];
$c = $a;
$b = 37;
echo $a['ID']."\n";
echo $b."\n";
echo $c['ID']."\n";

outputs 37, 37, 37

In both cases $b is a reference to $a['ID'] while $c is a pointer to the same object as $a.

When $b changes $a['ID'] and $c['ID'] change because assigning $b changes the value referenced by $a['ID'].

When $c['ID'] changes, a new int is assigned to $a['ID'], $b doesn't reference $a['ID'] anymore.

But this itches me

$a = new ArrayObject();
$a['ID'] = 42;
$b = &$a['ID'];
$c = $a;
$c['ID'] &= 0;
$c['ID'] |= 37;
echo $a['ID']."\n";
echo $b."\n";
echo $c['ID']."\n";

(outputs 37, 37, 37)

Is this defined behaviour? I didn't see anything about that in the documentation...


Solution

  • Let's take this code as a basis: ( refcounting documentation )

    $a = new ArrayObject();
    $a['ID'] = 42;
    $b = &$a['ID'];
    $c = $a;
    
    xdebug_debug_zval('a');
    xdebug_debug_zval('b');
    xdebug_debug_zval('c');
    

    This gives:

    a:
    (refcount=2, is_ref=0),
    object(ArrayObject)[1]
      public 'ID' => (refcount=2, is_ref=1),int 42
    b:
    (refcount=2, is_ref=1),int 42
    c:
    (refcount=2, is_ref=0),
    object(ArrayObject)[1]
      public 'ID' => (refcount=2, is_ref=1),int 42
    

    As you say: $a is an object, $b is a reference of $a['ID'] ( $a['ID'] and $b : refcount=2, is_ref=1) and $c is copy as a reference (since PHP5), so $c is a reference of $a ( it's now the same object : refcount=2, is_ref=0)


    If we do: $c['ID'] = 37;

    We get:

    a:
    (refcount=2, is_ref=0),
    object(ArrayObject)[1]
      public 'ID' => (refcount=1, is_ref=0),int 37
    b:
    (refcount=1, is_ref=0),int 42
    c:
    (refcount=2, is_ref=0),
    object(ArrayObject)[1]
      public 'ID' => (refcount=1, is_ref=0),int 37
    

    $c['ID'] is assigned a new int so =>

    $b becomes independent (refcount=1 and is_ref=0), as well as $a['ID'] and $c['ID']

    BUT as $c and $a are dependent, $a['ID'] and $c['ID'] take the same value 37.


    Now, let's take the base code and we do: $c['ID'] &= 0;

    UPDATE: Unexpectedly, we get:

    a:
    (refcount=2, is_ref=0),
    object(ArrayObject)[1]
      public 'ID' => (refcount=2, is_ref=1),int 0
    b:
    (refcount=2, is_ref=1),int 0
    c:
    (refcount=2, is_ref=0),
    object(ArrayObject)[1]
      public 'ID' => (refcount=2, is_ref=1),int 0
    

    instead of: ( if: $c['ID'] = $c['ID'] & 0;)

    a:
    (refcount=2, is_ref=0),
    object(ArrayObject)[1]
      public 'ID' => (refcount=1, is_ref=0),int 0
    b:
    (refcount=1, is_ref=0),int 42
    c:
    (refcount=2, is_ref=0),
    object(ArrayObject)[1]
      public 'ID' => (refcount=1, is_ref=0),int 0
    

    ArrayObject implements ArrayAccess so:

    As said in the comment and documented here:

    A direct modification is one that replaces completely the value of the array dimension, as in $obj[6] = 7. An indirect modification, on the other hand, only changes part of the dimension, or attempts to assign the dimension by reference to another variable, as in $obj[6][7] = 7 or $var =& $obj[6]. Increments with ++ and decrements with -- are also implemented in a way that requires indirect modification.

    A possible answer:

    "Combined Operator (+=, -=, &=, |=) could be worked as the same manner (indirect modification.)":

    refcount and is_ref are not impacted therefore (in our case) the values of all related variables are modified. ($c['ID'] => $a['ID'] => $b)