Skip to content
Theory · Loops · lesson 4 of 6

The foreach loop

So far the runner has counted laps on their own: a counter $i, a condition and a step. But often you need something else — to walk through a ready set of values: the prices of the items in a cart, the grades for a test. PHP has the foreach loop for this.

Picture a row of boxes along the running track. Each holds one thing — say, the price of an item. The runner walks along the row from the first box to the last and at each one receives a photocopy of what lies inside. There are as many iterations as there are boxes.

Here is the sum of the prices in a cart:

PHP
<?php

$prices = [10, 20, 30];
$total = 0;
foreach ($prices as $price) {
    $total += $price;
}
echo $total, "\n"; // 60

Let's walk through it line by line:

  1. $prices = [10, 20, 30]; puts three prices into one variable — a row of three boxes.
  2. $total = 0; sets up the sum.
  3. foreach ($prices as $price) walks along the row: on each iteration, $price receives a copy of the next price.
  4. $total += $price; adds that price to the sum.
  5. After three iterations, echo prints 60.

There is no counter, condition or step here: foreach itself knows where the row starts and where it ends. If the row is empty, [], the body never runs.

A list in square brackets

The notation [10, 20, 30] creates a list, a set of values stored in order. Each value in it is called an element.

The elements are numbered from zero: the first has number 0, the second 1, the third 2. An element's number is called its index. In PHP a list is a special case of an array: that is what the language calls a set of values in one variable.

An element is read by its index in square brackets, and the count function returns the number of elements:

PHP
echo $prices[0], ' ', $prices[2], "\n"; // 10 30
echo count($prices), "\n"; // 3

Note: there are three elements, but the last index is 2. The index of the last element is always one less than count.

The same walk can also be written with for and an index: for ($i = 0; $i < count($prices); $i++) with $prices[$i] in the body. foreach is shorter and leaves no room for boundary mistakes: you do not have to remember that the indexes run from 0 to count minus one.

The loop variable is a copy

On each iteration, foreach puts a copy of the element into $price, not the element itself. That is why assigning to $price does not change the list:

PHP
foreach ($prices as $price) {
    $price = $price * 2;
}
echo $prices[0], ' ', $prices[1], "\n"; // 10 20
echo $price, "\n"; // 60

Let's take apart what happened:

  1. On each iteration, $price receives a copy: 10, then 20, then 30.
  2. $price = $price * 2; doubles the copy, while the box in the row stays the same.
  3. After the loop, the list still holds 10, 20 and 30.
  4. $price lives on after the loop, like $i after for: it holds the last doubled copy, 60.

Interviewers like to ask whether foreach changes the elements when you assign something to the loop variable. The answer: no, the loop variable is a copy, and the list stays as it was.

The photocopy picture holds as long as the loop has no & sign. The notation foreach ($prices as &$price) hands the box itself to the loop instead of a copy, and then an assignment changes the element. After such a loop $price stays bound to the last element of the list; this lesson does not need the notation.

Common mistake

Someone tries to walk through a string with foreach to get its letters:

PHP
$word = 'php';
foreach ($word as $letter) { // ✗
    echo $letter, "\n";
}
TEXT
Warning: foreach() argument must be of type array|object, string given

A string is not a list, and foreach does not walk through it. The message says that the loop accepts an array or an object, and it got a string.

This is a warning, not a stop: the body never runs, and the lines after the loop go on working. To walk through letters, put them in a list: foreach (['p', 'h', 'p'] as $letter).

Key points

  • [10, 20, 30] is a list: its elements are stored in order, indexes start at 0, and count returns the number of elements.
  • foreach ($prices as $price) walks through all the elements in order, with no counter or condition.
  • The loop variable is a copy of the element: assigning to it does not change the list.
  • foreach does not walk through a string: PHP gives a Warning, and the body does not run.