Reputation: 4771
I trying to understand PHP reference, but I found a problem, when working with chained references.
class A
{
public $val;
public function __construct($val)
{
$this->val = $val;
}
}
$values = array(
'a' => new A('a'),
'b' => new A('b'),
'c' => new A('c')
);
$values['a'] = &$values['b'];
$values['b'] = &$values['c'];
Return:
array(
'a' => new A('b'),
'b' => new A('c'),
'c' => new A('c')
)
Why 'val' in object A for key 'a' is 'b'? I expected that value will be 'c'. Thanks
Upvotes: 2
Views: 108
Reputation: 33501
$values['b']
didn't exist when you assigned it to $values['a']
. If you change the order, it works as you describe:
~$ php -a
php > $values = array();
php > $values['c'] = 1;
php > $values['b'] = &$values['c'];
php > $values['a'] = &$values['b'];
php > print_r($values);
Array
(
[c] => 1
[b] => 1
[a] => 1
)
Upvotes: 1
Reputation: 2874
Try to avoid references, they may get you in hard-to-debug troubles. Also, objects are referenced behind the scenes; that means this:
$a1 = new A('a');
$a2 = $a1;
is similar to:
$b1 = new A('a');
$b2 = &$b1;
In the way, that:
$a2->val = 1; // $a1->val is now equal to 1, because $a1 and $a2 are pointing
// to the same instance
$b2->val = 1; // $b1->val is now equal to 1, because $b2 points to $b1
There is a subtle difference though:
$a1 = 1; // $a2 still points to A object
$b1 = 1; // $b2 still points to $a1 which points to number 1,
// therefore $b2 == 1
Also it works a bit differently with arrays because array assignment always involves value copying.
If you want to understand what is happening in your example, let's take a look:
So your original array is this:
$values = array(
'a' => new A('a'),
'b' => new A('b'),
'c' => new A('c')
);
Let's take a look what happens in there step by step:
$values['a'] = &$values['b']; // $values['a'] is now reference to new A('b')
// that means your original new A('a') is now
// lost
$values['b'] = &$values['c']; // $values['b'] is now reference to new A('c')
// stored under 'c' key, that means $values['b']
// is now equal to $values['c'] ; note that this is
// different than $b2 = &$b1; from the above example
// since we use an array and not bare variables
// the $values['a'] points to value stored under
// the 'b' key, but we replace the 'b' key value
// as opposed to giving it a new value;
// "Array assignment always involves value copying."
// So you ended up with this result:
array(
'a' => new A('b'),
'b' => new A('c'), // this is actually reference to 'c', just wrote new A()
// to keep this part consistent with your question's
// formatting
'c' => new A('c')
)
Upvotes: 1
Reputation: 97688
Note: this answer is to the question as originally asked. The new question is somewhat different, although the use of objects doesn't change the fundamental behaviour in this case.
PHP references cannot be "chained" - the =&
operator is not like a pointer reference/de-reference mechanism, it binds two (or more) variables together. But importantly neither variable name is more "real" than the other - they are both (or all) references to a nameless value.
For example, $foo =& $bar
takes the value pointed at by $bar
and also points $foo
at that value. If you then assign to either variable (e.g. $foo = 42;
or $bar = 42;
), the value is updated. Some actions, such as unset($bar)
are acting on the variable, not the value, so they affect only that particular variable, not the referenced value.
You can see this in action by running this: $foo = 42; $bar =& $foo; unset($foo); echo $bar;
Note that $foo
was the "original" name for the value, but even after it is unset, the value still exists. You can then reuse the variable $foo
for a completely different value, without affecting $bar
in any way.
If you take a variable that was already a reference, and make it a reference to something else, that is like unsetting the reference and reusing the variable name to set a new one. So the value is unaffected, and previously referenced variable names become independent.
In your code, you do this:
// 1: Make $values['a'] point to the value currently pointed at by $values['b']
$values['a'] =& $values['b'];
// 2: Make $values['b'] point to the value currently pointed at by $values['c']
// the previous value is now only pointed to by $values['a']
$values['b'] =& $values['c'];
// 3: Set the value shared by $values['b'] and $values['c'] to 1
$values['c'] = 1;
(As a technical detail, what I've been calling here "value" is a structure within PHP's internals called a zval
.)
Edit for updated question:
Objects may seem to make this more complicated, as they have an extra level of indirection, which works in almost the same way: $foo = new A('a'); $bar = $foo;
still creates two values (zval
s) but those values are both pointers to the same object.
In your case, this makes no difference, as you are still using reference assignment (=&
) - and if you didn't, you would get the same result:
// 1: Make the value of $values['a'] point to the object currently pointed at by the value of $values['b']
$values['a'] = $values['b'];
// 2: Make the value of $values['b'] point to the object currently pointed at by the value of $values['c']
// the previous object is now only pointed to by the value of $values['a']
$values['b'] = $values['c'];
We now have three things we can change: the variable, the value it points at, and the object that that points at:
// Change the object: updates the object shared by $values['b'] and $values['c']
$values['c']->value = 42;
// Change the value: will only update $values['b'] if =& was used to tie them together
$values['c'] = 42;
// Change the variable: will never affect $values['b']
unset($values['c']);
Upvotes: 0