Manipulate an array when looping - php

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.

Related

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

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.

PHP: replace array value doesn't stay after foreach loop

I'm changing the value in a multi-dimensional array and it's not staying outside of the foreach loop that's being used to traverse it.
My array initially looks something like this:
Array
{
[0] => Array
{
[name] => Bob
[age] => 33
[state] => CA
[visited] => 0
}
...
}
My PHP gets into it by going:
foreach ($people as $person){
echo $person['name']
....
logic for the visited variable
...
$person['visited'] = $calculated_visit_value;
}
If I
print_r($person)
at the end (but inside) of the foreach loop everything looks good, the value for visited is set. However, if I print_r($people) outside of the loop, $person['visited'] is not set. I don't know what I'm doing wrong.
Help is appreciated.
You are creating a new variable called $person from within that for loop and your array will never see the scope of that new variable.
You can try passing it by reference, like so:
foreach ($people as &$person){
echo $person['name'];
....
logic for the visited variable
...
$person['visited'] = $calculated_visit_value;
}
From foreach's documentation:
Unless the array is referenced, foreach operates on a copy of the
specified array and not the array itself. foreach has some side
effects on the array pointer. Don't rely on the array pointer during
or after the foreach without resetting it.
What this means is that your $person variable is a copy of what was in the array, similar in effect to this code (note that this code is for understanding only and wrong on many levels, in reality you would use the reset(), current() and next() function to loop properly over your array, see here):
for ($i = 0; $i < count($people); $i++) {
$person = $people[$i];
// code inside your foreach ...
}
So if you change the content of $person, you don't actually modify what's inside the $people array
To solve that, you can either use a referenced foreach:
foreach ($people as &$person) { // note the &
$person = $calculated_visit_value; // $person is now a reference to the original value inside $people and thus this will work as intended
}
Note that the refence is not cleared when the foreach loop ends, so at the end of it $person is still a reference to the last element of $people.
If you don't know what references are, please refer to the documentation for more info.
Or use the key to access the original array:
foreach ($people as $person_index => $person) {
$people[$person_index] = $calculated_visit_value;
}
For your information, you can use the two together
foreach ($people as $person_index => &$person { ...
The $person array is generated on each iteration, so setting that value would be overwritten on the next go through anyway.
But even so, that array only exists during the loop. You should create another array before the loop and put your values into that array during the loop.
As it has been told, "you are creating a new variable called $person from within that for loop and your array will never see the scope of that new variable."
I find this solution more robust :
foreach ($people as $key => $person)
{
echo $person['name'];
//logic for the visited variable
$people[$key]['visited']=$calculated_visit_value;
}

Foreach() value is set, after value disappears

I am trying to add a new key to an existing numerical indexed array using a foreach() loop.
I wrote this piece of code:
foreach($new['WidgetInstanceSetting'] as $row){
$row['random_key'] = $this->__str_rand(32, 'alphanum');
debug($row);
}
debug($new);
The first debug() works as I expected: the 'random_key' is created in the $new array.
Now, the problem is that the second debug() shows the $new array, but without the newly added key.
Why is this happening? How can I solve this problem?
$row ends up being a copy in the scope of the foreach block, so you really are modifying a copy of it and not what's in the original array at all.
Stick a & in your foreach to modify the $row array within your $new array by reference:
foreach($new['WidgetInstanceSetting'] as &$row){
And as user576875 says, delete the reference to $row in case you use that variable again to avoid unwanted behavior, because PHP leaves it around:
foreach($new['WidgetInstanceSetting'] as &$row){
$row['random_key'] = $this->__str_rand(32, 'alphanum');
debug($row);
}
unset($row);
debug($new);
Use the & to get a by reference value that you can change.
foreach($new['WidgetInstanceSetting'] as &$row){
$row['random_key'] = $this->__str_rand(32, 'alphanum');
debug($row);
}
debug($new);
You need to access the element by reference if you want to modify if within the array, as follows:
foreach($new['WidgetInstanceSetting'] as &$row) {
$row['random_key'] = $this->__str_rand(32, 'alphanum');
}
you are not creating random_key in $new array you are creating it in $row

Simplify looping through an array which could be flat or multidimensional

Often, I will have an array holding a set of values each of which I need to process. Sometimes, the array will only hold a single set, in which case each value needs to be put through the process. Other times, the array will hold many sets, in which case each value will be an array and each value of those arrays will need to be processed. Here is an example:
foreach($array as $key => $value) {
if(is_array($value)) {
foreach($value as $subkey => $subvalue) {
//Process $subvalue here
}
}
else {
//Process $value here
}
}
The problem is that the code for processing $value/$subvalue is identical except that it operates on a different variable. One way to simplify this would be to put that code into a function but it still seems inelegant to have to call it twice. Furthermore, that would leave quite a lot of code (the foreach loops and array test) outside of that function. For example, say the process is validation, I don't want to have to write two foreach loops and an array test whenever I want to call my validation function.
Is there a simpler way of doing this?
You can use a RecursiveArrayIterator to iterate over the values in the array, e.g.
$iterator = new RecursiveIteratorIterator(
new RecursiveArrayIterator($yourArray),
RecursiveIteratorIterator:SELF_FIRST);
Then just foreach over it. Further reading:
http://www.phpro.org/tutorials/Introduction-to-SPL.html#7
Wrap the single value into an array, then proceed as usual:
foreach($array as $key => $value) {
if(!is_array($value)) $value = array($value);
foreach($value as $subkey => $subvalue) {
//Process $value/$subvalue here
}
}
Alternatively you can create a function that handles the processing of single items, then call that same function from each branch. You'll still end up saving writing the process twice.

Categories