Reference in the foreach - php

Having the reference in the foreach, what does it mean and what the benefit?
For example:
foreach ($delivery as &$store) {
$store = ClassName::FunctionName($store);
}
unset($store);
I never really use reference when I do some coding in PHP.

If you don't pass by reference into the foreach loop, any changes and updates won't automatically be retained in the initial data structure after the loop has completed.
For example:
$test = array('cat'=>'meow','dog'=>'woof');
foreach($test as $a){
$a='test';
}
print_r($test);
In this case, the array will still contain:
array('cat'=>'meow','dog'=>'woof');
However in this example using references:
$test=array('cat'=>'meow','dog'=>'woof');
foreach($test as &$a){
$a='test';
}
var_dump($test);
...the array will contain:
array('cat'=>'test','dog'=>'test');

Related

When is foreach with a parameter by reference dangerous?

I knew, that it can be dangerous to pass the items by reference in foreach.
In particular, one must not reuse the variable that was passed by reference, because it affects the $array, like in this example:
$array = ['test'];
foreach ($array as &$item){
$item = $item;
}
$item = 'modified';
var_dump($array);
array(1) {
[0]=>
&string(8) "modified"
}
Now this here bite me: the content of the array gets modified inside the function should_not_modify, even though I don't pass the $array by value.
function should_not_modify($array){
foreach($array as &$item){
$item = 'modified';
}
}
$array = ['test'];
foreach ($array as &$item){
$item = (string)$item;
}
should_not_modify($array);
var_dump($array);
array(1) {
[0]=>
&string(8) "modified"
}
I'm tempted to go through my whole codebase and insert unset($item); after each foreach($array => &$item).
But, since this is a big task and introduces a potentially useless line, I would like to know if there is a simple rule to know when foreach($array => &$item) is safe without a unset($item); after it, and when not.
Edit for clarification
I think I understand what happens and why. I also know what is best to do against: foreach($array as &$item){...};unset($item);
I know that this is dangerous after foreach($array as &$item):
reuse the variable $item
pass the array to a function
My question is: Are there other cases that are dangerous, and can we build an exhaustive list of what is dangerous. Or the other way round: is it possible to describe when it is not dangerous.
About foreach
First of all, some (maybe obvious) clarifications about two behaviors of PHP:
foreach($array as $item) will leave the variable $item untouched after the loop. If the variable is a reference, as in foreach($array as &$item), it will "point" to the last element of the array even after the loop.
When a variable is a reference then the assignation, e.g. $item = 'foo'; will change whatever the reference is pointing to, not the variable ($item) itself. This is also true for a subsequent foreach($array2 as $item) which will treat $item as a reference if it has been created as such and therefore will modify whatever the reference is pointing to (the last element of the array used in the previous foreach in this case).
Obviously this is very error prone and that is why you should always unset the reference used in a foreach to ensure following writes do not modify the last element (as in example #10 of the doc for the type array).
About the function that modifies the array
It's worth noting that - as pointed out in a comment by #iainn - the behavior in your example has nothing to do with foreach. The mere existence of a reference to an element of the array will allow this element to be modified. Example:
function should_not_modify($array){
$array[0] = 'modified';
$array[1] = 'modified2';
}
$array = ['test', 'test2'];
$item = & $array[0];
should_not_modify($array);
var_dump($array);
Will output:
array(2) {
[0] =>
string(8) "modified"
[1] =>
string(5) "test2"
}
This is admittedly very suprising but explained in the PHP documentation "What References Do"
Note, however, that references inside arrays are potentially dangerous. Doing a normal (not by reference) assignment with a reference on the right side does not turn the left side into a reference, but references inside arrays are preserved in these normal assignments. This also applies to function calls where the array is passed by value. [...] In other words, the reference behavior of arrays is defined in an element-by-element basis; the reference behavior of individual elements is dissociated from the reference status of the array container.
With the following example (copy/pasted):
/* Assignment of array variables */
$arr = array(1);
$a =& $arr[0]; //$a and $arr[0] are in the same reference set
$arr2 = $arr; //not an assignment-by-reference!
$arr2[0]++;
/* $a == 2, $arr == array(2) */
/* The contents of $arr are changed even though it's not a reference! */
It's important to understand that when creating a reference, for example $a = &$b then both $a and $b are equal. $a is not pointing to $b or vice versa. $a and $b are pointing to the same place.
So when you do $item = & $array[0]; you actually make $array[0] pointing to the same place as $item. Since $item is a global variable, and references inside array are preserved, then modifying $array[0] from anywhere (even from within the function) modifies it globally.
Conclusion
Are there other cases that are dangerous, and can we build an exhaustive list of what is dangerous. Or the other way round: is it possible to describe when it is not dangerous.
I'm going to repeat the quote from the PHP doc again: "references inside arrays are potentially dangerous".
So no, it's not possible to describe when it is not dangerous, because it is never not dangerous. It's too easy to forget that $item has been created as a reference (or that a global reference as been created and not destroyed), and reuse it elsewhere in your code and corrupt the array. This has long been a topic of debate (in this bug for example), and people call it either a bug or a feature...
The accepted answer is the best, but I'd like to give a complement: When is unset($item); not necessary after a foreach($array as &$item) ?
$item: if it is never reused after, it cannot harm.
$array: the last element is a reference. This always dangerous, for all the reasons already stated.
So what does change that element form being a reference to a value ?
the most cited: unlink($item);
when $item falls out of scope when the array is returned from a function, then the array becomes 'normal' after being return from the function.
function test(){
$array = [1];
foreach($array as &$item){
$item = $item;
}
var_dump($array);
return $array;
}
$a = test();
var_dump($a);
array(1) {
[0]=>
&int(1)
}
array(1) {
[0]=>
int(1)
}
But beware: if you do anything else before returning, it can bite !
You can break the reference by "json decode/encode"
function should_not_modify($array){
$array = json_decode(json_encode($array),false);
foreach($array as &$item){
$item = 'modified';
}
}
$array = ['test'];
foreach ($array as &$item){
$item = (string)$item;
}
should_not_modify($array);
var_dump($array);
The question is purely academic, and this is a bit of a hack. But, it's sort of fun, in a stupid programming way.
And of course it outputs:
array(1) {
[0]=>string(4) "test"
}
As a side the same thing works in JavaScript, which also can give you some wonky-ness from references.
I wish I had a good example, because I've had some "weird" stuff happen, I mean like some quantum entanglement stuff. This one time at a PHP camp, I had a recursive function ( pass by reference ) with a foreach ( pass by reference ) and well it sort of ripped a hole in the space time continuum.

Manipulate an array when looping

So $tr['tree'] is an array. $dic is an array stored as key values. I want to add the key source to that those arrays. It looks like the following code doesn't work as expected as I'm guessing $dic is a new instance of the array object inside $tr['tree'].
foreach($tr['tree'] as $dic){
$dic['source'] = $tr['source']." > ".$dic['name'];
}
Note, I'm coming from python where this would work brilliantly. So how would I do this in PHP?
foreach() creates copies of the items you're looping on, so $dic in the loop is detached from the array. If you want to modify the parent array, the safe method is to use:
foreach($array as $key => $value) {
$array[$key] = $new_value;
}
You could use a reference:
foreach($array as &$value) {
^---
$value = $new_value;
}
but that can lead to stupidly-hard-to-find bugs later. $value will REMAIN a reference after the foreach terminates. If you re-use that variable name later on for other stuff, you'll be modifying the array, because the var still points at it.

Unexpected behavior with references

I have some unexpected behavior with references:
foreach ($this->data as &$row)
{
$row['h'] = 1;
}
foreach ($this->data as $id => $row)
{
... in some cases $row[$id] = $row;
}
The result is that the last element of the array is replaced with the second to last element of the array. It is fixed with the following code:
foreach ($this->data as $key => $row)
{
$this->data[$key]['h'] = 1;
}
Unfortunately, I don't have more time to spend on this. Maybe it is an error with PHP (PHP 5.5.9-1ubuntu4) or something I don't know about references?
There is a perfectly logical explanation and this is not a bug!
PHP 5 introduces the possibility of modifying the contents of the array directly by assigning the value of each element to the iterated variable by reference rather than by value. Consider this code, for example:
$a = array (’zero’,’one’,’two’);
foreach ($a as &$v) {
}
foreach ($a as $v) {
}
print_r ($a);
It would be natural to think that, since this little script does nothing to the array, it will not affect its contents... but that’s not the case! In fact, the script provides the following output:
Array
(
[0] => zero
[1] => one
[2] => one
)
As you can see, the array has been changed, and the last key now contains the value ’one’. How is that possible? The first foreach loop does not make any change to the array, just as we would expect. However, it does cause $v to be assigned a reference to each of $a’s elements, so that, by the time the loop is over, $v is, in fact, a reference to $a[2].
As soon as the second loop starts, $v is now assigned the value of each element. However, $v is already a reference to $a[2]; therefore, any value assigned to it will be copied automatically into the last element of the arrays! Thus, during the first iteration, $a[2] will become zero, then one, and then one again, being effectively copied on to itself. To solve this problem, you should always unset the variables you use in your by-reference foreach loops—or, better yet, avoid using the former altogether.
When looping over an array by reference, you need to manually let go of the reference at the end of your for loop to avoid weird behaviors like this one. So your first foreach should be:
foreach ($this->data as &$row)
{
.... code ....
}
unset($row);
In this case, unset is only destroying the reference, not the contents referenced by $row.
See the warning in the PHP foreach documentation

Strange foreach loop after modifying array

As I wrote some code, PHP confused me a little as I didn't expected the result of the following code:
$data = array(array('test' => 'one'), array('test' => 'two'));
foreach($data as &$entry) {
$entry['test'] .= '+';
}
foreach($data as $entry) {
echo $entry['test']."\n";
}
I think it should output
one+
two+
However the result is: http://ideone.com/e5tCsi
one+
one+
Can anyone explain to me why?
This is expected behaviour, see also https://bugs.php.net/bug.php?id=29992.
The reference is maintained when using the second foreach, so when using the second foreach the value of $entry, which points still to $data[1], is overwritten with the first value.
P.s. (thanks to #billyonecan for saying it): you need to unset($entry) first, so that your reference is destroyed.
This is mentioned specifically in the documentation for foreach. You should unset the loop variable when it gets elements of the array by reference.
Warning
Reference of a $value and the last array element remain even after the
foreach loop. It is recommended to destroy it by unset().

Using foreach effectively in PHP

So I don't think I'm making full use of the foreach loop. Here is how I understand foreach.
It goes like foreach(arrayyouwanttoloopthrough as onevalueofthatarray)
No counter or incrementing required, it automatically pulls an array, value by value each loop, and for that loop it calls the value whatever is after the "as".
Stops once it's done with the array.
Should basically replace "for", as long as dealing with an array.
So something I try to do a lot with foreach is modify the array values in the looping array. But in the end I keep finding I have to use a for loop for that type of thing.
So lets say that I have an array (thing1, thing2, thing3, thing4) and I wanted to change it....lets say to all "BLAH", with a number at the end, I'd do
$counter = 0;
foreach($array as $changeval){
$counter++;
$changeval = "BLAH".$counter;
}
I would think that would change it because $changeval should be whatever value it's at for the array, right? But it doesn't. The only way I could find to do that in a] foreach is to set a counter (like above), and use the array with the index of counter. But to do that I'd have to set the counter outside the loop, and it's not even always reliable. For that I'd think it would be better to use a for loop instead of foreach.
So why would you use foreach over for? I think I'm missing something here because foreach has GOT to be able to change values...
Thanks
OH HEY One more thing. Are variables set in loops (like i, or key) accessible outside the loop? If I have 2 foreach loops like
foreach(thing as value)
would I have to make the second one
foreach(thing2 as value2) ]
or else it would have some problems?
You can use a reference variable instead:
foreach ($array as &$value)
{
$value = "foo";
}
Now the array is full of foo (note the & before $value).
Normally, the loop variable simply contains a copy of the corresponding array element, but the & (ampersand) tells PHP to make it a reference to the actual array element, rather than just a copy; hence you can modify it.
However, as #Tadeck says below, you should be careful in this case to destroy the reference after the loop has finished, since $value will still point to the final element in the array (so it's possible to accidentally modify it). Do this with unset:
unset($value);
The other option would be to use the $key => $value syntax:
foreach ($array as $key => $value)
{
$array[$key] = "foo";
}
To answer your second question: yes, they are subsequently accessible outside the loop, which is why it's good practice to use unset when using reference loop variables as in my first example. However, there's no need to use new variable names in subsequent loops, since the old ones will just be overwritten (with no unwanted consequences).
You want to pass by reference:
$arr = array(1, 2, 3, 4);
foreach ($arr as &$value)
{
$value = $value * 2;
}
// $arr is now array(2, 4, 6, 8)
The extended foreach syntax is like this:
foreach ($array as $key => $value) {
}
Using the $key, you can index the original array:
foreach ($array as $key => $value) {
$array[$key] = "BLAH";
}
Incrementing from 0 to ...
foreach($array as $changeval){
if (!isset($counter)) { $counter = 0; }
$counter++;
$changeval = "BLAH".$counter;
}
Using the index/key of the ARRAY
foreach($array as $key => $changeval){
$changeval = "BLAH".$key;
}
You can use the key when looping with foreach:
foreach ($array as $key => $value)
{
$array[$key] = "foo";
}
But note that using a reference like Will suggested will be faster for such a case - but anyway, the $key => $value-syntax is quite useful sometimes.

Categories