PHP Conditionals and Loops
AI-assistedDrafted with AI assistance and reviewed before publishing; examples are version-pinned. Verify against official docs when in doubt.
PHP control flow looks familiar, with two PHP-specific twists worth knowing early: the match expression (PHP 8, strictly better than switch in most cases) and the alternative syntax used inside templates.
if / elseif / else
$score = 85;
if ($score >= 90) {
echo "excellent";
} elseif ($score >= 60) {
echo "pass";
} else {
echo "fail";
}
elseif and else if are equivalent; the one-word form is the convention.
Ternary and null coalescing:
$label = $score >= 60 ? "pass" : "fail";
$name = $_GET["name"] ?? "anonymous"; // falls back only when null or unset
?? chains: $a ?? $b ?? "default".
Why switch is fragile
switch ($status) {
case "pending":
echo "waiting";
break;
case "done":
echo "done";
break;
default:
echo "unknown";
}
switch uses == (loose comparison) and requires a manual break — forget one and execution falls through into the next case. "1" matches 1, which is a recurring source of bugs.
match (preferred)
PHP 8’s match fixes all three: strict comparison, no fall-through, and it returns a value.
$status = "pending";
$message = match ($status) {
"pending" => "waiting",
"done" => "finished",
"failed" => "failed",
default => "unknown",
};
echo $message; // waiting
For range-style conditions, match against true:
$level = match (true) {
$score >= 90 => "excellent",
$score >= 60 => "pass",
default => "fail",
};
With no matching arm and no default, match throws UnhandledMatchError — a safer failure than switch silently skipping everything.
Rule of thumb: match for equality-style dispatch, switch only when you deliberately want fall-through.
for
Use it when the number of iterations is known:
for ($i = 0; $i < 3; $i++) {
echo $i . "\n";
}
Do not use for to walk arrays: PHP array keys are not guaranteed to be consecutive integers, so you will miss entries or hit undefined offsets.
foreach (the default choice)
$colors = ["red", "green", "blue"];
foreach ($colors as $color) {
echo $color . "\n";
}
foreach ($colors as $index => $color) {
echo "$index => $color\n";
}
To modify elements, iterate by reference:
foreach ($colors as &$color) {
$color = strtoupper($color);
}
unset($color); // break the reference
Skipping unset leaves $color bound to the last element, so any later use of that name quietly edits the array — a nasty bug to track down.
while and do-while
while when the count is unknown:
$retry = 0;
while ($retry < 3) {
echo "attempt " . ($retry + 1) . "\n";
$retry++;
}
do-while always runs once:
do {
$input = trim(fgets(STDIN));
} while ($input === "");
Before writing a while, confirm the exit condition is actually reachable.
break and continue
foreach ([1, 2, 3, 4, 5] as $n) {
if ($n === 2) {
continue; // skip this iteration
}
if ($n === 4) {
break; // exit the loop
}
echo $n . "\n";
}
// prints 1 and 3
Both accept a level to act on enclosing loops:
foreach ($rows as $row) {
foreach ($row as $cell) {
if ($cell === null) {
continue 2; // continue the outer loop
}
echo $cell;
}
}
Numeric levels are easy to miscount; past two levels, extract a function instead.
Alternative syntax for templates
Braces buried in HTML are hard to read. PHP offers a colon form with matching end* keywords:
<?php if ($loggedIn): ?>
<p>Welcome back</p>
<?php else: ?>
<p><a href="/login">Sign in</a></p>
<?php endif; ?>
<ul>
<?php foreach ($items as $item): ?>
<li><?= htmlspecialchars($item, ENT_QUOTES, "UTF-8") ?></li>
<?php endforeach; ?>
</ul>
<?=is shorthand for<?php echoand is always available since PHP 5.4.- Anything printed into HTML must be escaped with
htmlspecialchars— see the forms chapter. - The pairs are
if:/endif;,foreach:/endforeach;,while:/endwhile;,for:/endfor;, andswitch:/endswitch;.
Worked example: group and render
<?php
declare(strict_types=1);
$orders = [
["id" => 1, "status" => "pending"],
["id" => 2, "status" => "done"],
["id" => 3, "status" => "pending"],
];
$groups = [];
foreach ($orders as $order) {
$groups[$order["status"]][] = $order["id"];
}
foreach ($groups as $status => $ids) {
$label = match ($status) {
"pending" => "Pending",
"done" => "Done",
default => "Other",
};
echo $label . ": " . implode(", ", $ids) . PHP_EOL;
}
// Pending: 1, 3
// Done: 2
Common mistakes
1. Missing break in switch
Execution continues into the next case. match removes the problem entirely.
2. Relying on switch’s loose comparison
"1" matches 1. match uses ===.
3. match without default
An unmatched value throws UnhandledMatchError. Always provide default unless throwing is intended.
4. Not unsetting a foreach reference
The last element stays aliased to your loop variable.
5. Using for on associative arrays
Keys are not consecutive; you get undefined offsets. Use foreach.
6. A while loop that never advances its variable
Infinite loop. Check the exit condition before running.
Summary
- Prefer
matchfor value dispatch: strict, no fall-through, returns a value. - Walk arrays with
foreach, neverfor. - Always
unsetaforeachby-reference variable. - In templates use
if: ... endif;with<?= ?>for output.
Next: functions, type declarations and scope.