Pregunta

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

¿Fue útil?

Solución

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)

Otros consejos

It's more or less defined (but sometimes undocumented) behaviour; mainly because $a is not an array but an ArrayObject.

Let's take a look at your third code fragment first:

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

The last assignment translates to:

$tmp = &$c->offsetGet('ID');
$tmp &= 0; // or: $tmp = $tmp & 0;

The take-away point here is only offsetGet() is called and it returns a reference to $c['ID'], as noted in this comment. Because offsetSet() is not called, the value of $b changes as well.

Btw, the increment (++) and decrement operator (--) work in a similar fashion, no offsetSet() is called.

Differences

This is different from your first example:

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

The last statement has the following equivalent:

$c->offsetSet('ID', 37);

Before a new value is assigned to $c['ID'], the previous value is effectively unset(); this is why $b is the only variable still holding on to 42.

Proof of this behaviour can be seen when you use objects instead of numbers:

class MyLoggerObj
{
        public function __destruct()
        {
                echo "Destruct of " . __CLASS__ . "\n";
        }
}

$a = new ArrayObject();
$a['ID'] = new MyLoggerObj();
$a['ID'] = 37;

echo $a['ID']."\n";

Output:

Destruct of MyLoggerObj
37

As you can see, the destructor is called on MyLoggerObj; that's because in this case there's no variable holding on to the value anymore.

Bonus

If you try to find out when offsetGet() and offsetSet() are called by extending ArrayObject you will be disappointed to find out that you can't properly mimic mixed &offsetGet($key);. In fact, doing so changes the behaviour of ArrayObject in such a way that it's impossible to prove the behaviour of this class.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top