Control Structures and Loops in PHP

if/elseif/else, switch vs match, and for/foreach/while/do-while — including the loop-in-a-loop gotchas that actually bite in real code.

By DevStudio Online Team · Published September 3, 2026

The building blocks are simple; the mistakes people actually make with them are specific and worth knowing in advance.

switch vs. PHP 8's match

// switch: loose comparison, falls through without `break`
switch ($status) {
    case 'active':
    case 'trial':
        $label = 'Live';
        break;
    default:
        $label = 'Inactive';
}
 
// match: strict comparison, no fall-through, returns a value directly
$label = match ($status) {
    'active', 'trial' => 'Live',
    default => 'Inactive',
};

match uses strict (===) comparison and throws an UnhandledMatchError if nothing matches and there's no default — which is usually what you want, since a silently-ignored switch with a missing break is one of the most common bug classes in PHP. Prefer match in any PHP 8+ codebase.

foreach by reference — the classic footgun

$numbers = [1, 2, 3];
 
foreach ($numbers as &$n) {
    $n *= 2;
}
unset($n); // <- easy to forget, and it matters
 
foreach ($numbers as $n) {
    $n += 100; // silently overwrites $numbers[2] because $n is still a reference!
}
 
print_r($numbers); // [2, 4, 106] — not [2, 4, 6] like you'd expect

After a foreach ... as &$n loop, $n stays bound to the last element of the array by reference. If you reuse the variable name $n in a later loop without unset()-ing it first, you'll silently corrupt the array. Always unset() the reference variable immediately after a by-reference foreach.

do-while: the one loop guaranteed to run at least once

$attempts = 0;
do {
    $attempts++;
    $success = tryConnect();
} while (!$success && $attempts < 3);

Use do-while specifically when the loop body needs to run before the condition can even be evaluated meaningfully — like a retry loop where you need the first attempt's result to decide whether to continue.

for vs. foreach

for ($i = 0; $i < count($items); $i++) { /* ... */ }   // needs the index
foreach ($items as $index => $item) { /* ... */ }       // needs index AND value, cleaner
foreach ($items as $item) { /* ... */ }                 // needs value only — prefer this

One real performance note: for ($i = 0; $i < count($items); $i++) calls count() on every single iteration. For large arrays, cache it once — $len = count($items); — before the loop starts, or just use foreach instead, which doesn't have this problem at all.

More PHP guides