Bug #29992 [Com]: foreach by reference corrupts the array
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: OK, I went over this some more. http://php.net/manual/en/control-structures.foreach.php * print_r($item); // would return 4 you you uncommented this. * unset($item); // This would remove the pointer to $clean[3]. Expected. **/ echo "A:\n"; /*A*/ print_r($clean); // $clean is still unmodified echo "B:\n"; foreach($clean as $item){ /*## * Using AS $item SETS $item TO the current $item value (a.k.a. $clean[0], etc.) * Essentially foreach($clean as $item) is short hand for something like: * $x=0;while($x < count($clean)){$item=$clean[$x]; ### your code ### $x++;} * The problem I had was that I did not expect foreach to be able to set on call **/ /*B*/ print_r($clean); } ?> So creating the variable is documented, and it isn't a bug. The ability to set the value could be made clearer though. Previous Comments: [2013-05-20 09:51:33] richard at ejem dot cz This IS a bug and SHOULD be finally fixed. It is weird, hard to debug and unexpected behavior which took me many hours of life finding the problem. Almost every modern programming language has a variable context, which guarrantees the foreach variable is not visible after exiting the loop. Anyone is using it intentionally for "weird reason"? come on guys, almost every bug can be used for some hacking or weird reasons, will it stop you from fixing other bugs? no! Sorry for "mee-to" like post, but I do it for the good of next PHP programmer generations who will also lose hours of their lives discovering this nonsense. Please fix it. ---------------- [2012-10-25 17:01:03] paul dot dillinger at gmail dot com I understand that my explanation above isn't 100% accurate (unset really doesn't have anything to do with it), but that doesn't change that the expected behavior is not working. Rasmus said: "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." I say in the example above, would you expect a print_r of the same array to return 1,2,2? My issue was that the content of the entire array. Honestly I've been programming since I was eight years old. That was 1985. If this was confusing the hell out of me then something's wrong. Here's an even simpler example: A and B output the same array differently with no modification (not expected). C and D are the same (expected). The only change was not re-using the name $item. How can we make it so that using &$item in one foreach and then using the same variable name ($item) in a different foreach does not change the original array? ------------ [2012-10-25 15:49:27] paul dot dillinger at gmail dot com I still say this is a bug, so here's all the code you need to re-produce it: 'myname1', 'attrib' => 'something1', 'package' => true, 'library_name' => 'package1' ), array( 'name' => 'myname2', 'attrib' => 'something2', 'package' => true, 'library_name' => 'package1' ), array( 'name' => 'myname3', 'attrib' => 'something3', 'package' => false ), array( 'name' => 'myname4', 'attrib' => 'something4', 'package' => false ) ); foreach($ext_js as &$item){ if(!empty($item['package'])){ $packages[] = $item; // Not using &, so should be a copy, not a reference corrent? $library_names[] = $item['library_na
Bug #29992 [Com]: foreach by reference corrupts the array
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: I understand that my explanation above isn't 100% accurate (unset really doesn't have anything to do with it), but that doesn't change that the expected behavior is not working. Rasmus said: "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." I say in the example above, would you expect a print_r of the same array to return 1,2,2? My issue was that the content of the entire array. Honestly I've been programming since I was eight years old. That was 1985. If this was confusing the hell out of me then something's wrong. Here's an even simpler example: A and B output the same array differently with no modification (not expected). C and D are the same (expected). The only change was not re-using the name $item. How can we make it so that using &$item in one foreach and then using the same variable name ($item) in a different foreach does not change the original array? Previous Comments: -------------------- [2012-10-25 15:49:27] paul dot dillinger at gmail dot com I still say this is a bug, so here's all the code you need to re-produce it: 'myname1', 'attrib' => 'something1', 'package' => true, 'library_name' => 'package1' ), array( 'name' => 'myname2', 'attrib' => 'something2', 'package' => true, 'library_name' => 'package1' ), array( 'name' => 'myname3', 'attrib' => 'something3', 'package' => false ), array( 'name' => 'myname4', 'attrib' => 'something4', 'package' => false ) ); foreach($ext_js as &$item){ if(!empty($item['package'])){ $packages[] = $item; // Not using &, so should be a copy, not a reference corrent? $library_names[] = $item['library_name']; unset($item); } } if(!empty($packages)){ /*A*/ print_r($ext_js); foreach($packages as $item){ /*B*/ print_r($ext_js); } } ?> Look at the output on the last item. Instead of unset removing the item from the array $ext_js (which is what I thought it would do), it corrupts the array. The array is fine though unless you go in to another foreach using another $item. Changing the variable name on the second foreach to something OTHER than $item (I used $itemm) fixes it. Bug. [2012-10-25 07:37:08] email at stevemann dot net I don't think this is going to go anywhere - seems to have reached a stalemate. So I have just retrained my head to automatically create foreach loops thus: foreach($array as $item){ }unset($item); If you need access to the last $item outside the loop, then just do it somewhere before the unset($item). Seems to me this thread is being accessed periodically by developers scratching their heads after discovering similar oddities happening with their foreach loops. My advice would be to do something similar to the above and just live with it. [2012-10-24 22:11:44] newms87 at gmail dot com I understand this functionality, and I do agree that it is not a bug. It seems at the core of PHP that this is what would happen, but it does seem very unintuitive to me having used a variety of other languages. The result is not expected and has caused several very hard to find bugs for me. Would it be possible to have PHP generate a E_NOTICE when using the same $var in both a foreach and afterwards when in a higher scope? EG: foreach($args as &$a){} $a = 'hello'; // this would generate an E_NOTICE Then maybe have the option to turn off (or on by default) the E_NOTICE warnings in the ini settings?
Bug #29992 [Com]: foreach by reference corrupts the array
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: I still say this is a bug, so here's all the code you need to re-produce it: 'myname1', 'attrib' => 'something1', 'package' => true, 'library_name' => 'package1' ), array( 'name' => 'myname2', 'attrib' => 'something2', 'package' => true, 'library_name' => 'package1' ), array( 'name' => 'myname3', 'attrib' => 'something3', 'package' => false ), array( 'name' => 'myname4', 'attrib' => 'something4', 'package' => false ) ); foreach($ext_js as &$item){ if(!empty($item['package'])){ $packages[] = $item; // Not using &, so should be a copy, not a reference corrent? $library_names[] = $item['library_name']; unset($item); } } if(!empty($packages)){ /*A*/ print_r($ext_js); foreach($packages as $item){ /*B*/ print_r($ext_js); } } ?> Look at the output on the last item. Instead of unset removing the item from the array $ext_js (which is what I thought it would do), it corrupts the array. The array is fine though unless you go in to another foreach using another $item. Changing the variable name on the second foreach to something OTHER than $item (I used $itemm) fixes it. Bug. Previous Comments: [2012-10-25 07:37:08] email at stevemann dot net I don't think this is going to go anywhere - seems to have reached a stalemate. So I have just retrained my head to automatically create foreach loops thus: foreach($array as $item){ }unset($item); If you need access to the last $item outside the loop, then just do it somewhere before the unset($item). Seems to me this thread is being accessed periodically by developers scratching their heads after discovering similar oddities happening with their foreach loops. My advice would be to do something similar to the above and just live with it. [2012-10-24 22:11:44] newms87 at gmail dot com I understand this functionality, and I do agree that it is not a bug. It seems at the core of PHP that this is what would happen, but it does seem very unintuitive to me having used a variety of other languages. The result is not expected and has caused several very hard to find bugs for me. Would it be possible to have PHP generate a E_NOTICE when using the same $var in both a foreach and afterwards when in a higher scope? EG: foreach($args as &$a){} $a = 'hello'; // this would generate an E_NOTICE Then maybe have the option to turn off (or on by default) the E_NOTICE warnings in the ini settings? [2012-06-29 18:52:20] iam4webwork at hotmail dot com I appreciate the explanation that Rasmus provides -- thank you! One small but troublesome detail: The first foreach changes the array by making $a[1] a reference variable while $a[0] remains a normal variable. $a = array(1,2); foreach($a as &$e){} var_dump($a,$e); // $a[1] == &int 2$e == 2 foreach($a as $e){} $a[1] == &int 1$e == 1 var_dump($a,$e); // $a[1] now points to last value of $a which is $a[0] How about adding a switch so that users who don't want or understand this behavior can turn it off? Then it would be up in front of the documentation and would be less liable to be overlooked by users who fail to scroll down to the colored box. Even if PHP were to have lexical scope (how hard would that be to implement and why can't PHP evolve that way?), that doesn't change the fact that the first loop doing seemingly nothing, does change the array. [2012-05-04 08:04:56] email at stevemann dot net ras...@php.net asked "Ok, simple question then. Do you expect this to output 3?" foreach(array(1,2,3) as $b) { } echo $b; I would much prefer it not to
Bug #29992 [Com]: foreach by reference corrupts the array
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, Thanks for looking at this. I found the problem. I would still call it a bug, but I will describe it and you can decide. You are the man after all. You were right, I was passing a variable by reference in the few lines of code in front of my example above: foreach($this->data['external_'.$type] as &$item){ if(!empty($item['package'])){ $packages[] = $item; $library_names[] = $item['library_name']; unset($item); } } /* Code in example above goes here */ BUT, where I see this as a bug was: $packages (the array that was getting changed) was a new array created from the data of each $item. $packages was never being referenced, though the variable $item it was created from was. So, it should be a copy of the data and not THE data right? I fixed it by simply not trying to pass by reference and changing unset($item) to unset($this->data['external_'.$type]). Looking at it, that was the way to do it from the beginning. I see that, but I'm not sure why $packages gets changed down the road (it was correct immediately after this, but it and all copies of it change inside the next foreach). Any thoughts? Previous Comments: [2012-03-01 18:52:14] ras...@php.net Paul, my guess is that $item is a reference to an element in the $packages array going into this loop. Try using a different variable there. -------------------- [2012-03-01 18:31:08] paul dot dillinger at gmail dot com 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']
Bug #29992 [Com]: foreach by reference corrupts the array
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] => a