SoFunction
Updated on 2025-03-10

PHP summary of knowledge points about foreach replication

PHP's foreach is a very neat and to the point of the language structure. Still some people don't like using it because they think it's slow. One reason for common name is that foreach copies the array it it iterates.

Therefore, some people suggest writing:

$keys = array_keys($array);
$size = count($array);
for ($i = 0; $i < $size; $i++) {
  $key  = $keys[$i];
  $value = $array[$key];
 
  // ...
}

Instead of being more intuitive and direct:

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

Here are two questions:

Microoptimization is bad. Often, it just wastes your time and does not lead to any measurable performance improvements.

The copying behavior of foreach is a little more complicated than most people think. Normally, the "optimized" version will be slower than the original version.

When will the foreach be copied?

Whether foreach copies an array and the number of copies depends on three things:

Whether the iterative array is referenced, how high its refcount is, and whether the iteration is done by reference.

No reference, refcount == 1

In the following code, $array is not referenced and refcount is 1. In this case, foreach does not copy the array (prove)—the contrary to the popular view that foreach always copies iterative arrays without references.

test();
function test() {
  $array = range(0, 100000);
  foreach ($array as $key => $value) {
    // ...
  }
}

The reason is simple: Why do you do this? The only place foreach to modify $array is that it is an internal array pointer. This is expected behavior, so there is no need to prevent it.

Unreferenced, refcount > 1

The following code looks very similar to the previous code. The only difference is that the array is now passed as a parameter. This seems like an irrelevant difference, but it does change the behavior of foreach:

It will now copy the array structure, not the value (provement; if you want to know that this is just the copied structure, compare this to that script. The first copy only the structure, and the second copy both).

$array = range(0, 100000);
test($array);
function test($array) {
  foreach ($array as $key => $value) {
    // ...
  }
}

This may look a little strange at first glance:

Why does it copy when an array is passed through an argument, but if it is defined in the function, it won't copy? The reason is that array zval is now shared among multiple variables: the $array variable outside the function and the $array variable inside the function. If foreach iterates over an array without copying the array structure, it will not only change the array pointer of the $array variable in the function, but also the pointer of the $array variable outside the function. Therefore foreach needs to copy the array structure (i.e. hash table). On the other hand, these values ​​can still share zvals, so no copying is required.

Quote

The next situation is very similar to the former situation. The only difference is that the array is passed by reference. In this case, the array will not be copied (prove).

$array = range(0, 100000);
test($array);
function test(&$array) {
  foreach ($array as $key => $value) {
    // ...
  }
}

In this case, the same reasoning applies to the former case: the outer $array and the inner $array share zvals. The difference is that they are now references (isref == 1), so in this case any changes to the inner array will be made to the outer array. So if the array pointer of the inner array changes, the array pointer of the outer array should also change. This is why foreach does not need to be copied.

Iterate through reference

The above examples are all iterated by value. For reference iterations, the same rules are applied, but the value-added reference changes the copy behavior of the array value (the behavior about structure copy remains the same).

The case "not referenced, refcount == 1" has not changed. Reference iteration means that if there is any change in the $ value, we want to change the original array so that the array is not copied (prove).

The "referenced" case also remains the same, in which case a change to $value should change all variables that reference the iterative array (proof).

Only the "unreferenced, refcount > 1" situation changed because the array structure and its values ​​are now required to be copied. Array structure, because otherwise the array pointer of the $array variable outside the function will change, and the change to the $value will also change the external $array value (proof).

Summarize

Foreach will copy the array structure if and only if the iterative array is not referenced and has refcount > 1

foreach will also copy the array value, provided and only if the previous point is applied and the iteration is done by reference