11

I need to loop through an array twice - once to modify its values, and once to display its content in html. Unfortunately, I am running into trouble. I have created a test scenario to illustrate the issue.

$cases = array(
  array('caseStyle' => 'case style 1', 'caseNum' => 'case01'),
  array('caseStyle' => 'case style 2', 'caseNum' => 'case02'),
  array('caseStyle' => 'case style 3', 'caseNum' => 'case03'),
  array('caseStyle' => 'case style 4', 'caseNum' => 'case04'),
  array('caseStyle' => 'case style 5', 'caseNum' => 'case05'),
  array('caseStyle' => 'case style 6', 'caseNum' => 'case06')
);

foreach ($cases as $k => &$v) {
    $v['caseNum'] = ucwords($v['caseNum']);
}

foreach ($cases as $k => $v) {
    echo $v['caseNum'] . ' - ' . $v['caseStyle'] . '<br/>';
}

This outputs:

Case01 - case style 1
Case02 - case style 2
Case03 - case style 3
Case04 - case style 4
Case05 - case style 5
Case05 - case style 5

Note the values for the last item are wrong.

If during the second iteration I use foreach($cases as $k => $d) instead of foreach($cases as $k => $v) or if I unset $v before the second iteration (unset($v)) everything goes fine. This is intriguing. What am I missing?

7
  • 3
    Why do you need to do this in two loops? Why not do both actions the first time through? Commented Sep 7, 2013 at 3:52
  • In the actual code the array is one bulky bad ass array of arrays with rows returned from the db. In the first iteration I cleanup, add defaults for missing values, change formats where needed and add additional fields pulled from other tables to it. The second iteration is when I am painting the html page with values from the array (this is legacy code - html and php all in one file, no templates :( ) Commented Sep 7, 2013 at 3:59
  • 1
    That still doesn't mean you can't do it all at once. Even if you need to use values from the array before displaying them, you can buffer the output into a variable and then echo out the variable when needed. If complexity is causing a problem, remove the complexity ;) Commented Sep 7, 2013 at 4:02
  • 1
    Your point is well taken. For that matter, simply using another variable in foreach or unsetting $v before the second iteration is solving the problem. However, that would be a blind solution. I need to understand what is happening here. Commented Sep 7, 2013 at 4:08
  • I've provided a thorough explanation of what's going on in an answer below. Commented Sep 7, 2013 at 4:35

3 Answers 3

22

When you execute a foreach loop, the variables in the () persist after the loop is done. That means that after the first loop finishes, you can var_dump($v) and get the values that were contained in it after the last iteration through the first loop. This is the case whether it's a reference (&$v) or a normal variable ($v).

However, if it's a reference in the first loop, it remains a reference unless it's unset. That means when you enter the second loop, you're overwriting the reference with the value of the array element you're currently looking at.

Remember, what foreach($cases as $k => $v) really means is "take the key for this element in $cases and assign that to $k, and take the value for this element and assign it to $v)". Since $v is still a reference to the last element in the array, rather than setting the value of a new variable $v, you're actually updating the value of where $v already points to.

What that means is, if we simplify $cases to be simply ['a', 'b', 'c', 'd'], after the first time through the second foreach, $cases is now ['a', 'b', 'c', 'a'] because you've reassigned the element in $cases that $v points to - the last one - to have the same value as the first one. The second time through, it's ['a', 'b', 'c', 'b']. The third time through it's ['a', 'b', 'c', 'c']. Then, the last time through, you're assigning it to itself, and at that time it holds the value 'c'.

This is really just a case of php working as expected. The solution is to unset($v) as soon as the first loop finishes, to make sure that the next time you use $v you're using a new variable, rather than an existing reference.

To see this in action:

Head on over to http://phpfiddle.org/ and paste the following code, and run it; you'll see in the output that $v is maintained after the first loop completes, and that the value of $cases[5] changes each time through the second loop.

$cases = array(
  array('caseStyle' => 'case style 1', 'caseNum' => 'case01'),
  array('caseStyle' => 'case style 2', 'caseNum' => 'case02'),
  array('caseStyle' => 'case style 3', 'caseNum' => 'case03'),
  array('caseStyle' => 'case style 4', 'caseNum' => 'case04'),
  array('caseStyle' => 'case style 5', 'caseNum' => 'case05'),
  array('caseStyle' => 'case style 6', 'caseNum' => 'case06')
);

foreach ($cases as $k => &$v) {
    $v['caseNum'] = ucwords($v['caseNum']);
}
var_dump($v);
echo "<br />";
foreach ($cases as $k => $v) {
    print_r($cases);
    echo "<br />";
    echo $k . ': ' . $v['caseNum'] . ' - ' . $v['caseStyle'] . '<br/>';
}
Sign up to request clarification or add additional context in comments.

2 Comments

Absolutely! Thanks DiMono. I slept thinking about it and just woke up with the same insight :)
You're welcome. It's good form to accept an answer once you've found it works, by the way.
2

Try this:

foreach ($cases as $k => &$v) {
    $cases[$k]['caseNum'] = ucwords($v['caseNum']);
    echo $cases[$k]['caseNum'] . ' - ' . $cases[$k]['caseStyle'] . '<br/>';
}

3 Comments

Hm also read that question and was trying to reproduce that on XAMPP. While the initial code wouldn't be my first attempt, I tried to figure out what's going wrong. Didn't come to a conclusion, but your solution definitely works.
This is what we've been discussing in the question's comments.
@undone - Thanks for your answer. Unfortunately, this is not usable in my original script. I need to do it twice. Unsetting $v between the 2 loops seems the best solution.
0

Let me know is this your solution:

foreach ($cases as $k => &$v){
    foreach($v as &$value){ $value = ucwords($value); }
echo $v['caseNum'] . ' - ' . $v['caseStyle'] . '<br/>';
}

2 Comments

Thanks for sharing. Once you pass $v by reference, all its children are automatically passed by reference. No need to explicitly do so.
In second foreach the $v variable treat as $cases! what you do is reference the values of $v into $value not copy of them.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.