Edit report at https://bugs.php.net/bug.php?id=29992&edit=1
ID: 29992 Comment by: paul dot dillinger at gmail dot com Reported by: fletch at pobox dot com Summary: foreach by reference corrupts the array Status: Not a bug Type: Bug Package: Scripting Engine problem Operating System: linux PHP Version: 5.0.1 Block user comment: N Private report: N New Comment: Rasmus, I think they might be having the same problem than I am where the array literally changes as soon as I enter the foreach. I've given an in depth explanation at: http://codeigniter.com/forums/viewthread/201487/ , but I'll give a summary here. I'm using a newer version of PHP (5.3.8) and foreach is corrupting my array even when it's not being passed by reference. My original code read something like this: if(!empty($packages)){ /* $this->data['external_js'] is normal */ foreach($packages as $item){ /* $this->data['external_js'] has changed */ I noticed that one of my javascript files that this function is packing in to a single package as not present. Even more odd was another was in the package twice. So I started logging the $this->data['external_js'] array to FirePHP to see where the error was happening. Strangely enough it happened immediately after a foreach. I decided to make a separate copy of the array as a "just in case" and report that. It changed the exact same way. I need to literally hand build my JS packages as I can't figure out any way to stop this array from changing once it enters the foreach. Here is the troubleshooting code with comments: if(!empty($packages)){ // checking to see if there are multiple files to be packaged together if($type=='js'){ // check to see if it's javascript as that was the package that had the problem $ext_js_for_firephp = $this->data['external_js']; // found that $this->data['external_js'] was changing so I assign it to a new variable exclusively for logging to FirePHP, this variable exists NO WHERE ELSE in the code. fb_log('$ext_js_for_firephp before', $ext_js_for_firephp); // Log to FirePHP /* fb_log function for reference function fb_log($Label,$Object=null){ $firephp = FirePHP::getInstance(true); if(empty($Object)){ $Object = $Label; $Label = NULL; } $firephp->log($Object, $Label); } */ } foreach($packages as $item){ // Starting the foreach if($type=='js'){ // Again problem was with JS package changing fb_log('$ext_js_for_firephp after', $ext_js_for_firephp); // Log to FirePHP, but now the value is different. } // AGAIN this happened before I started logging the vars, so logging is not causing the issue. It's not an error with the logging output, as this is exactly what the file being built had in it. /* RESULT */ /* Before FirePHP returns: $ext_js_for_firephp before = array( [0] => array( ['template_id'] => 30 ['js_id'] => 9 ['id'] => 9 ['library_name'] => 'modernizr' ['file_name'] => 'modernizr.min.js' ['version_major'] => 2 ['version_minor'] => 0 ['version_build'] => 6 ['static'] => 1 ['package'] => 0 ['footer'] => 0 ['priority'] => 100 ) [1] => array( ['template_id'] => 30 ['js_id'] => 12 ['id'] => 12 ['library_name'] => 'default' ['file_name'] => 'default.js' ['version_major'] => 0 ['version_minor'] => 0 ['version_build'] => 4 ['static'] => 1 ['package'] => 1 ['footer'] => 0 ['priority'] => 90 ) [2] => array( ['template_id'] => 37 ['js_id'] => 11 ['id'] => 11 ['library_name'] => 'jquery-ui-custom' ['file_name'] => 'jquery-ui-1.8.11.custom.min.js' ['version_major'] => 1 ['version_minor'] => 8 ['version_build'] => 11 ['static'] => 1 ['package'] => 0 ['footer'] => 0 ['priority'] => 0 ) ) */ /* After FirePHP returns: $ext_js_for_firephp after = array( [0] => array( ['template_id'] => 30 ['js_id'] => 9 ['id'] => 9 ['library_name'] => 'modernizr' ['file_name'] => 'modernizr.min.js' ['version_major'] => 2 ['version_minor'] => 0 ['version_build'] => 6 ['static'] => 1 ['package'] => 0 ['footer'] => 0 ['priority'] => 100 ) [1] => array( ['template_id'] => 30 ['js_id'] => 12 ['id'] => 12 ['library_name'] => 'default' ['file_name'] => 'default.js' ['version_major'] => 0 ['version_minor'] => 0 ['version_build'] => 4 ['static'] => 1 ['package'] => 1 ['footer'] => 0 ['priority'] => 90 ) [2] => array( ['template_id'] => 30 ['js_id'] => 12 ['id'] => 12 ['library_name'] => 'default' ['file_name'] => 'default.js' ['version_major'] => 0 ['version_minor'] => 0 ['version_build'] => 4 ['static'] => 1 ['package'] => 1 ['footer'] => 0 ['priority'] => 90 ) ) */ Previous Comments: ------------------------------------------------------------------------ [2012-02-09 17:20:44] ras...@php.net What do you mean you con't care about the explanation? Ok, simple question then. Do you expect this to output 3? foreach(array(1,2,3) as $b) { } echo $b; If you do, then you don't want us to fix this "bug" because fixing it would mean $b is not 3 here. ------------------------------------------------------------------------ [2012-02-09 16:55:31] looris at gmail dot com No one cares about the technical explanation about why this happens at a low level. I'm quite puzzled you are still in denial about this bug. ------------------------------------------------------------------------ [2012-02-09 16:49:22] ras...@php.net No matter how you paint this, it isn't a bug. What you are really asking for is some sort of block-level scoping. As such, it should be proposed as a feature request, but it would be a major change in the language. The simplest way to illustrate that is to just unroll the loops. Take this example: $a = array(1,2); foreach($a as &$b) { } foreach($a as $b) { } print_r($a); The problem here is that people find it inconsistent that the 2nd loop changes $a. But if we unroll the loops and write the exactly equivalent code without the foreach construct we get: $a = array(1,2); // First loop, $b is a reference to each element in $a $b = &$a[0]; $b = &$a[1]; // Second loop, $b is assigned the value of each element in $a $b = $a[0]; $b = $a[1]; Those two pieces of code are identical in every way. The thing that confuses people is that $b is still a reference $a[1] going into the second loop. Since PHP doesn't have block-level scoping, there is nothing in the language that would permit $b to be unset between the two loops without introducing a major inconsistency. In fact there is plenty of code that relies on this fact which would break if we made such an arbitrary change. I suppose what you are asking for is syntax along the lines of: $a = array(1,2); { local $b = &$a[0]; $b = &$a[1]; } { local $b = $a[0]; $b = $a[1]; } Where $b is locally scoped in each of those blocks and it might look like this in a foreach case: $a = array(1,2); foreach($a as local &$b) { } foreach($a as local $b) { } Without such a scoping syntax change, something as simple as: forach(array(1,2,3) as $b) { } echo $b; where the code fully expects $b to be 3 would break. ------------------------------------------------------------------------ [2012-02-09 12:33:40] robbie at shapehq dot co dot uk I feel very strongly that this behavior is wrong. It's not consistent with other languages and will cause great confusion! ------------------------------------------------------------------------ [2011-10-20 04:56:50] bruce at kaskubar dot com With all due respect to those who spend their time developing, debugging, and explaining PHP, BALDERDASH! Elsewhere, those of us who continue to claim "bug" are supposed to be chastened by all the explanations that have been provided over the years. The fact that the reports persist and the explanations grow is evidence contrary to the finding of Bogus and in support of what is expected behavior. Fletch's example, for example, is real and reproducible through at least v5.3.2. How in the world can it be expected for a (second) loop and its object to perform as though no related instructions were executed previously, and then for that interaction to raise its ugly head for only the last iteration? It cannot be. It can be explained. But so can an earthquake be. That doesn't make it expected. I am unaware of any other case where prior use of a variable affects subsequent use of the same-named variable where its value is being explicitly reset or, as in the case of foreach, implicitly reset by virtue of its purpose. (That is, we can use $i as a loop counter here and as a file handle there and as long as we don't cross their purposes in the space-time continuum, all is well.) The only bogus thing about this bug report and all its cousins is their shared status. ------------------------------------------------------------------------ The remainder of the comments for this report are too long. To view the rest of the comments, please view the bug report online at https://bugs.php.net/bug.php?id=29992 -- Edit this bug report at https://bugs.php.net/bug.php?id=29992&edit=1