Reputation: 2307
Could someone explain to me why by-reference variable passing behaves in the way I am witnessing?
Here is a method from a controller I am working on:
public function view($view,$context=array()){
// <snip>
foreach($context as $a=>$b){
$$a = $b;
}
// <snip>
}
It copies the data $context
array into the local scope so that when the view is called (included) it can access the elements.
$foo->method(); // first thing
$bar->method(); // second thing
Being a bit of a fusspot, I wanted to work with the actual objects rather than copies (which just seemed wasteful) so I changed it to this:
public function view($view,$context=array()){
// <snip>
foreach($context as $a=>$b){
$$a =& $b;
}
// <snip>
}
That was when I witnessed something I was not expecting.
$foo->method(); // second thing!!
$bar->method(); // second thing
In the test case two objects are passed to the view which then outputs relevant data. When passing by reference rather than by value both variables ended up with references to the second object.
I did not expect that to happen. I would very much like someone to explain to me why that happened. I've probably missed something obvious so please educate me.
In case it is relevant (I suspect it might be but I am not sure).
The method is called like this:
$data = array();
$data['foo'] =& $this->module()->get_foo();
$data['bar'] =& $this->module()->get_bar();
$this->view('nameOfView',$data);
The gets return by-reference in this instance so the & here is probably overkill. Again, I am not as certain as I would like to be. For the purposes of this question I just really want to understand what is going on with the reference overwriting in the view method but feel free to school me on anything else I should know but clearly don't.
Upvotes: 1
Views: 1215
Reputation: 23830
Because the variable $b
is being reused by the foreach
loop.
And as $b
is reassigned, so are all references to it.
Simple example:
$a = [1, 2, 3];
$c = [];
foreach($a as $b)
{
$c[] = &$b;
}
print_r($c);
Which yields:
Array
(
[0] => 3
[1] => 3
[2] => 3
)
You can even take it a step further and do an assignment after your foreach
loop:
$b = 'derp';
which will turn your array into this:
Array
(
[0] => derp
[1] => derp
[2] => derp
)
Now, as stated twice in the comments so far, there is a function called extract()
, which seems to have been made exactly for what you're trying to do, and which is the way I suggest to go with.
But for the sake of completeness, it is fairly simple to "fix" your code.
There are two ways to do this:
Make $b
a reference instead of a copy.
You can do that by using $a as &$b
as argument to foreach
:
$a = [1, 2, 3];
$c = [];
foreach($a as &$b)
{
$c[] = &$b;
}
print_r($c);
Break the reference to $b
before it is reassigned.
You can do that by calling unset($b);
at the end of your loop:
$a = [1, 2, 3];
$c = [];
foreach($a as $b)
{
$c[] = &$b;
unset($b);
}
print_r($c);
Both of the above will then give you what you initially expected:
Array
(
[0] => 1
[1] => 2
[2] => 3
)
Note that when using the first way and you modify $a
after this, $c
might change as well, but not always.
For example, a direct assignment ($a[0] = 5;
) will affect $c
($c[0] == 5
).
$c
remains unaffected by any other action on $a
(as far as I can tell), but after messing with indices in $a
(as with array_shift()
or shuffle()
), $c[1]
might be a reference to $a[0]
, etc.
If you don't want a headache, just go with extract()
.
Upvotes: 3