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

Add new comment