6 FACTS YOU SHOULD KNOW ABOUT FOREACH LOOPS

 

 

Almost every programming language supports foreach loops, and PHP is no exception. I bet you have used it a lot of times too.

There’s nothing special about foreach loops in PHP, but I think there are a few facts of which you should be aware. Loop control structures are used all the time, so it is important not to make mistakes when using them.

 

In today’s post I show you 6 facts that every PHP developer should know about foreach loops.

 

 

 

 

 

Foreach loop

 

 

1. KEY INT CASTING

 

 

PHP has some user friendly features that should help beginners and less experienced developers code more easily. One of these is type juggling: the PHP interpreter automatically sets variable’s types depending on the context and on which operators are used.

This doesn’t mean that PHP doesn’t have types. Actually, each variable does have its own type, but it can change over time.

 

$a = 1 makes $a an integer, and $a = ‘1’ makes $a a string. In both cases, even if the type is different, a statement like $a = $a + 1 makes $a becomes an integer.

While type juggling can be convenient, it’s important to always consider the variable type we are dealing with, expecially when using strict comparison operators (like “===“). Strict comparison operators are very useful because they can differentiate between values like 0, FALSE or NULL, while loose comparison operators (llike “==“) consider them equivalent.

 

Let’s get to the point. Inside foreach loops you can access every key/value pair of the array you are iterating, and sometimes you may need to compare the key with some other value. Here’s the thing: if, and only if, the key is a numeric literal, then it is automatically converted into an integer at the start of the loop.

Therefore, strict comparing it to a string will always return FALSE.

 

The following example clearly shows the problem: 

 


/* We define a two-elements array. Note that the second key is a numeric literal string. */
$array = array(
   'first' => 'first element',
   '2' => 'second element'
);

/* Here is what happens */
foreach ($array as $key => $value)
{
   echo 'Key is: ' . $key . '<br>';
   echo 'Key type is: ' . gettype($key) . '<br>';
   
   /* Let's try a comparison */
   if ($key === '2')
   {
      echo 'Key is "2"!<br>';
   }
   else
   {
      echo 'Key is NOT "2"!<br>';
   }
}

The output is:

Key is: first
Key type is: string
Key is NOT “2”!
Key is: 2
Key type is: integer
Key is NOT “2”!

 

As you can see in the output, while the second array key is actually a string (‘2’), it is automatically casted to an integer inside the loop because it is a numeric literal. If we try to strict compare it to ‘2’, the expression returns false.

 

One solution to this problem is to explicity cast the key to string at the beginning of each loop (on line 13):


/* We define a two-elements array. Note that the second key is a numeric literal string. */
$array = array(
   'first' => 'first element',
   '2' => 'second element'
);

/* Here is what happens */
foreach ($array as $key => $value)
{
   $key = strval($key);
   echo 'Key is: ' . $key . '<br>';
   echo 'Key type is: ' . gettype($key) . '<br>';
   
   /* Let's try a comparison */
   if ($key === '2')
   {
      echo 'Key is "2"!<br>';
   }
   else
   {
      echo 'Key is NOT "2"!<br>';
   }
}

This time the output is:

Key is: first
Key type is: string
Key is NOT “2”!
Key is: 2
Key type is: string
Key is “2”!

 

Now the strict comparison between the second key and ‘2’ returns true, as we expect.

 

 

 

2. MODIFY AN ARRAY FROM INSIDE THE LOOP

 

 

 

If you are using foreach to iterate thorugh an array and you want to modify the array itself, there are two ways to do that.

The first one is to point to the array element you want to edit with the $array[$key] syntax (so you have to use the foreach $key => $value syntax in the foreach head).

The other way is to define $value as a reference and edit it directly.

 

The following two examples show how do do it:


$array = array(1, 2);

foreach ($array as $key => $value)
{
   $array[$key] += 1;
}

print_r($array);


<?php

$array = array(1, 2);

foreach ($array as $key => &$value)
{
   $value += 1;
}

print_r($array);

In both cases the output will be:

Array
(
[0] => 2
[1] => 3
)

 

 

3. VARIABLES OVERRIDE

 

 

It’s important to be aware that foreach loops do not have their own scope. This means that variables defined outside the loop are also available inside it, and any variable declared inside the loop will continue to be accessible even after the loop ends.

This also applies to the $key => $value variables used in the foreach head.

 

This example shows what can happen: 

 


$array = array(1, 2);

$value = 'my value';

foreach ($array as $key => $value)
{
   // do something
}

echo $value; /* Output is "2" */

 

After the loop, $value won’t contain ‘my value’ anymore but the value 2 instead, as it has been overwritten by the foreach assignment.

 

While I was searching for more insights on this problem, I found this interesting post that highlights another potential danger.

 

Let’s see an example first:  


$array = array(1, 2, 3);

foreach ($array as $key => &$value)
{
   // do something
}

/* Everything ok here */
print_r($array);

foreach ($array as $key => $value)
{
   // do something
}

/* Something wrong! */
print_r($array);

The output from the two print_r() is:

Array
(
[0] => 1
[1] => 2
[2] => 3
)
Array
(
[0] => 1
[1] => 2
[2] => 2
)

 

After the second foreach loop, the last element inside the array has changed. What happened?

In the first foreach we use the &$value syntax, so $value is set as a reference to each array’s element. In the last loop, $value is therefore equivalent to $array[2].

After the foreach ends, $value continues to be accessible (because foreach does not have its own scope).

When the second foreach starts, $value is still bound to the array’s last element, so in every loop both $value and $array[2] are overwritten.

 

This is what happens in more detail:

  • After the first foreach, $value points to $array[2].
  • Second foreach, first iteration: $array[2] is set with the first array element (so, $array[2] = 1).
  • Second foreach, second iteration: $array[2] is set with the second array element (so, $array[2] = 2).
  • Second foreach, third iteration: $array[2] is set with the third array element (i.e. itself, so $array[2] = 2).

That explains the result.

In order to avoid this problem, is always a good idea to unset any variable used in foreach loops, expecially when using references.

 

 

 

4. IS RESET() NEEDED?

 

 

Before PHP 7, foreach relied on the internal array pointer for looping, so it was a good idea to use reset() afterwards (and, before PHP 5, before starting the loop too).

This is not the case anymore with PHP 7, as foreach makes now use of its own internal pointer. This saves us a few lines of code.

 

The following example shows that after the foreach loop the array’s internal pointer still points to the first element:

 


$array = array(1, 2, 3);

foreach ($array as $item)
{
   echo 'Item: ' . $item . '<br>';
}

/* Current item is still 1 */
echo 'Current item: ' . current($array); /* Outputs 1 */

 

 

 

5. FOREACH VS FOR VS WHILE PERFORMANCE

 

Stopwatch

 

 

 

When you need to iterate through an array, you can also use a while loop or a for loop instead of using foreach.

Which way is the best in terms of performance?

 

I ran a test with an array of 1 million items. When using integer keys, it turns out that all three control structures run in about the same time (0.02 seconds on my test machine).

However, if we are dealing with associative arrays (i.e., arrays with string keys), the foreach loop is more than twice as fast as the other two control structures. This is probably caused by the fact that while and for structures need to make use of auxiliary function to retrieve the array’s elements, like current() and next().

Foreach is usually the easiest way to iterate through arrays, and it turns out to be the fastest too, so it should be your preferred choice.

It’s worth mentioning, however, that in real life scenarios the performance difference will be almost negligible, unless you need to deal with really huge arrays (and in that case, you should probably fix your application first…).

If you want to look at the test code just click down here to show it.

 

 

Control structures test

/* Set this to avoid memory errors */
ini_set('memory_limit','1024M');


$array = array();

/* Use this for creating a numeric array */
for ($i = 0; $i < 1000000; $i++)
{
   $array[$i] = $i;
}

/* Or use this for creating an associative array */
for ($i = 0; $i < 1000000; $i++)
{
   $array[$i] = strval($i) . 'string';
}

$count = count($array);
$start = microtime(TRUE);

/* Foreach loop, ok for both array types */
foreach ($array as $item)
{
   $var = $item;
}

/* For loop only for numeric arrays */
for ($i = 0; $i < $count; $i++)
{
   $var = $array[$i];
}

/* While loop only for numeric arrays */
$i = 0;
while ($i < $count)
{
   $var = $array[$i];
   $i++;
}

/* For loop for associative arrays */
for ($item = current($array); $item !== FALSE; $item = next($array))
{
   $var = $item;
}

/* While loop for associative arrays */
$item = current($array);
while ($item !== FALSE)
{
   $var = $item;
   $item = next($array);
}

$end = microtime(TRUE);
echo ($end - $start);

 

 

 

6. KEY => VALUE PERFORMANCE

 

 

PHP Foreach loops support both the $array as $value and the $array as $key => $value syntax.

The first one is also the fastest in terms of performance: in my test, using the second syntax resulted in a 50% longer execution time. If you don’t need the keys you should probably stick with the first syntax.

Even so, the numbers are so small that it’s unluckily that you would notice any difference in real life scenarios, so don’t worry about it too much (on my test machine, the two tests ran in about 0.2 and 0.3 seconds, for a 10 millions elements array).

 

 

That’s it. If you want some other example or have any questions just leave a comment below.

 As always, I really thank you for reading this post, and if you liked it please take a second to share it!

 

Alex