Call unset() after a PHP foreach loop with values passed by reference

I just spent a couple of hours debugging something really counterintuitive, where PHP’s print_r seemingly told me that an array had different content to the content that the same array contained according to a foreach loop. Consider this slightly contrived example code:

<?php
$fields = array( 'a' => 0, 'b' => 0, 'c' => 0, 'd' => 0, 'e' => 0, 'f' => 0 );
$i = 0;
foreach( $fields as $key => &$value )
{
	$value = ++$i;
}
print_r( $fields );
foreach( $fields as $key => $value )
{
	echo "$key => $value\n";
}

Now, call me crazy, but I expect both the print_r call and the second foreach loop to return pretty much the same information: the keys, a to f, and their corresponding values, 1 to 6. If you also expected that, you would be wrong. This is what you actually get:

Array
(
	[a] => 1
	[b] => 2
	[c] => 3
	[d] => 4
	[e] => 5
	[f] => 6
)
a => 1
b => 2
c => 3
d => 4
e => 5
f => 5

Yes, that’s right, the final key inexplicably appears to have the same value as the previous key, despite print_r suggesting otherwise.

Okay, so it’s not really inexplicable, just very unexpected. The reason is that the first foreach loop passes values by reference (&$value) while the second passes them by value ($value). As the PHP documentation states, “Reference of a $value and the last array element remain even after the foreach loop. It is recommended to destroy it by unset().”

Why? I have no idea. My intuition strongly suggests to me that $value should go out of scope after the first foreach loop, but apparently my intuition is wrong. Maybe PHP 7 will fix this madness, but at time of writing the stock PHP versions in Debian Jessie and Ubuntu Wily behave as outlined above.

Anyway, the short version is that you can avoid this by adding unset( $value ); immediately after the first foreach.

Comments

Alexandre Sinicio - Tue, 23/03/2021 - 11:21

Permalink

Just an update to a five-year-old post: just got kicked in the ass by the exact same problem on PHP7.4.

One would expect that the $key/$value vars on a foreach loop should fall out of scope after its done.
Or at the very least, when a foreach loop is created, either an error would be thrown when trying to use already-existing-vars for key/val or the variables would be restarted from scratch (any old ones would automatically be unset).

This is completely valid, because you are updating your $fields values by reference. This _changes_ your array. If you don't want this behaviour, just remove the ampersand.

I also just ran into this issue. This is NOT intended behavior. the first loop changes the array, which is expected behavior, but the the issue is re-using the variable '$value' in the second loop and the fact that that variable itself doesn't lose scope after the first loop. The array itself is fine. if you use '$value2' in the second loop, everything displays just fine. I guess it boils down to why doesn't PHP automatically unset the $value variable after the first loop, since that is its scope? what possible reason would be needed to keep that variable set, and why wouldn't the second loop overwrite it?

Add new comment

CAPTCHA