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] [email protected]
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'] => 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
)
)
*/
------------------------------------------------------------------------
[2012-02-09 17:20:44] [email protected]
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] [email protected]
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.
------------------------------------------------------------------------
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