Garbage collector problem after foreach loop ?
In PHP5, it is possible to loop through an array, and alter each item on the fly via the “by-reference” operator: the &-symbol. However, you should be on the lookout for unexpected behaviour.
First the initial data:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | $items = array( array( "field" => "field1", "value" => "value1", "options" => array( "operator" => "=", "logic" => "OR" ) ), array( "field" => "field2", "value" => "value2", "options" => array( "operator" => "=", "logic" => "OR" ) ), array( "field" => "field3", "value" => "value3", "options" => array( "operator" => "=", "logic" => "OR" ) ), array( "field" => "field4", "value" => "value4", "options" => array( "operator" => "=", "logic" => "OR" ) ) ); |
It is a simple array. It could be an array of where-clauses you wish to define for your own search module (like this example), or it could just simply be test data for a unit test.
In my case, this data comes into a function, and in that function I want to strip some of the data. The “logic” index under “options” is deprecated, and I want to remove it. To make my code compact, I have decided to alter each filter on the fly, by using the by-reference operator.
1 2 3 4 5 | foreach($items as &$item){ unset($item["options"]["logic"]); } print_r($items); |
This results in the following altered version of my initial data:
Array ( [0] => Array ( [field] => field1 [value] => value1 [options] => Array ( [operator] => = ) ) [1] => Array ( [field] => field2 [value] => value2 [options] => Array ( [operator] => = ) ) [2] => Array ( [field] => field3 [value] => value3 [options] => Array ( [operator] => = ) ) [3] => Array ( [field] => field4 [value] => value4 [options] => Array ( [operator] => = ) ) )
As you can see, I have successfully removed the “logic” index by using an ampersand in my foreach loop. This is potentially very useful functionality. It’s very easy to manipulate your data like this. No more fiddling around with indexes, or even create temporary arrays to house your data. Just edit it on the fly. There is however a (big) flaw, and I’ll illustrate it below.
In the previous loop, I have altered my array, and now I want to loop through it again, and just output each item in the array. Nothing more. But things go awry quite unexpectantly.
1 2 3 4 5 6 | $counter = 1; foreach($items as $item){ echo "Item {$counter}: "; print_r($item); $counter++; } |
The output of this loop is:
Item 1: Array ( [field] => field1 [value] => value1 [options] => Array ( [operator] => = ) ) Item 2: Array ( [field] => field2 [value] => value2 [options] => Array ( [operator] => = ) ) Item 3: Array ( [field] => field3 [value] => value3 [options] => Array ( [operator] => = ) ) Item 4: Array ( [field] => field3 [value] => value3 [options] => Array ( [operator] => = ) )
The last output isn’t what I expected it to be. As you can see, it is the same output as the previous item “3″.
I have encountered this problem in production code, and only by rigorously outputting all my data, I have discovered that this was my problem. I am not a PHP expert by far. So I don’t always know how things technically work. My guess is that after a foreach loop, with the by-reference operator, the garbage collector doesn’t do its job well. But I’m not sure this is the correct explanation. I have found a workaround though. After the first foreach, where you use the & symbol, you just unset that variable. Like this:
1 2 3 4 | foreach($items as &$item){ unset($item["options"]["logic"]); } unset($item); |
Now the output should be what you expect it to be. Just to be sure, here’s what I got:
// ... Item 4: Array ( [field] => field4 [value] => value4 [options] => Array ( [operator] => = ) )
If anyone has an idea to whether or not this is a bug, please let me know. I have so far not filed a bug (yet), as I feel I need to do a bit more research first. It could be something by-design, and then I’m making quite a fool of myself. I guess that’s just part of the learning process




I’ve seen a blog post mentioning it before (http://teknoid.wordpress.com/2008/10/03/php5-and-tiny-foreach-improvement/) but i too have no idea if the behaviour is intended or a bug.
I guess it has something to do with the variable being added to the global scope.
I’ll stick with the keys method as long as the reference variable needs to be unset because it too easy to forget to do it.