PHP from the ground up

Loops

Repeating work a known number of times with for, an unknown number with while, and the two statements that skip an iteration or leave the loop early.

Part 6 of 13

Beginner14 min readBack to Courses

For, when the count is known

A for loop has three parts in one line: where it starts, how long it continues, and what changes each time — for ($i = 1; $i <= 5; $i++). Read it as "start at one; while it is no more than five; add one". The middle part is a condition, so it can be any honest test, not only a count.

The counter is an ordinary variable, which is also the classic hazard: changing $i inside the body, or forgetting the $i++, is how a loop runs forever. When a loop will not end, the first thing to check is the third part of the header.

The counter survives the loop. After for ($i = 1; $i <= 5; $i++), $i is 6 — the value that failed the condition, not 5. That is useful when the count itself is the answer, and a surprise when you print the counter and get one more than the last item. If the loop might not run at all, the variable is whatever the initializer set, or undefined if there was none.

While, when it is not

A while loop checks its condition before each pass — "keep going while the fuel lasts". A do ... while checks after, which guarantees the body runs at least once; it is the shape for "ask, then decide whether to ask again".

Both loops need something in the body that eventually makes the condition false — the fuel has to burn. A while (true) is legitimate when an inner break is the real exit, but that break should be the first thing a reader can find.

Foreach, the loop for arrays

Arrays have their own loop, and it is the one you will write most. foreach ($rows as $row) hands you each value in order without an index, so there is no off-by-one and no count() in the header. When the keys matter, foreach ($row as $key => $value) gives you both, which is how a record is printed field by field.

The loop variable is a copy of the value, not a reference to the slot. Changing $row inside the body leaves the array untouched; to change the original you need foreach ($rows as &$row) and an unset($row) afterwards to break the reference. That ampersand is easy to leave behind, and a stray reference then changes the last element the next time the variable is assigned.

Break, continue, and nesting

Inside any loop, break leaves it immediately and continue skips to the next pass. They are how a loop expresses "stop when it is found" and "ignore the ones that do not matter", instead of checking the condition again after every line.

A loop inside a loop is fine — rows and columns, weeks and days — and it is worth knowing that break 2 leaves both at once, though a named flag is usually easier to read than a counted break. The next chapter replaces a whole class of loops with array functions; after it, the loops you keep are the ones with genuinely sequential work.

Counting, draining, and skipping

<?php
// for: a known number of passes.
for ($i = 1; $i <= 5; $i++) {
    echo $i . ' ';
}
echo PHP_EOL;

// while: until the tank runs dry.
$fuel = 3;
while ($fuel > 0) {
    echo "fuel $fuel" . PHP_EOL;
    $fuel--;
}

// continue skips a pass; break leaves the loop.
for ($n = 1; $n <= 10; $n++) {
    if ($n % 2 === 0) {
        continue;          // even numbers: not interesting
    }
    if ($n > 7) {
        break;             // eight and above: stop entirely
    }
    echo $n . ' ';
}
echo PHP_EOL;

// A triangle, one nested loop per dimension.
for ($row = 1; $row <= 4; $row++) {
    echo str_repeat('*', $row) . PHP_EOL;
}

A savings plan, year by year

Run it and read the table. Then change the interest rate, and add a line that stops the loop early once the balance passes 1400 — with break, not by changing the condition.

Output
Click "Run" to execute your code

A loop that will not end is not mystery: the condition still being true after the body means something in the body was supposed to change it. Print the condition's pieces inside the loop and the answer is on the screen.

When each part runs

A for header has three parts with different schedules: the initializer runs once, the condition is checked before every pass, and the increment runs after every pass. A while checks before its body; a do ... while checks after, which is why the body is guaranteed one run. Choose the shape by the question you are asking, not by habit.

break leaves the innermost loop, and break 2 leaves two levels; continue skips the rest of the current pass and goes back to the condition or increment. A loop that never ends is usually a condition that nothing in the body changes — print the pieces of the condition inside the loop and the missing change is visible.

Three loop mistakes account for most hangs and off-by-ones: forgetting the increment, starting at 1 while indexing an array that starts at 0, and changing an array inside foreach by value and expecting the iteration to see it. Check the header, check the first and last index, and use a reference only when you mean to change the original.

Try these next

  • Print a 5 by 5 multiplication table with a nested for loop.
  • Sum the odd numbers from 1 to 20 with continue, then write the same sum without it.
  • Halve 100 until it drops below 10 with while, printing each step.

A stock reorder report

Run the report, then add a fourth item with enough stock that it is skipped. Change the week loop to print only Monday, Wednesday and Friday with `continue`.

Output
Click "Run" to execute your code

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

In for ($i = 1; $i <= 5; $i++), which part keeps the loop from running forever?