Chapter 5: Flow Control
Programs choose between paths, repeat work, and bail out of work using flow control. PHP provides several constructs for branching and looping, each tuned for a slightly different kind of decision. This chapter introduces the most useful patterns and how to write them clearly.
if, elseif, else
The classic way to branch is to test a condition and execute one of several blocks. Each condition should read like a true or false statement; when a condition is true, the related block runs.
Writing a clear chained if ladder
Use a single chained ladder for mutually exclusive branches. The first condition that evaluates to true selects the block and the rest are skipped.
<?php
$score = 73;
if ($score >= 90) {
echo "A";
} elseif ($score >= 80) {
echo "B";
} elseif ($score >= 70) {
echo "C";
} else {
echo "Try again";
}
?>
$count > 0 instead of just $count when you mean "more than zero".
Using blocks and scoping correctly
Always use braces, even for a single statement; this prevents accidental binding to the wrong if when you add more lines later.
<?php
$isMember = true;
if ($isMember) {
$discount = 0.15;
echo "Member discount applied";
}
?>
Combining conditions safely
Group conditions with parentheses to make intent obvious and to control evaluation order.
<?php
$inStock = true;
$vip = false;
$quantity = 3;
if (($inStock && $quantity >= 1) || $vip) {
echo "Proceed to checkout";
}
?>
if ($x = 1) assigns the value and then evaluates as true; prefer === for equality with type safety.
match Expressions
A match expression selects a result based on a tested value and returns that result. It uses strict comparison for its arms and it must be exhaustive; this encourages precise branching.
Returning a value from match
Because match is an expression, it produces a value that you can assign or echo directly.
<?php
$status = 404;
$message = match ($status) {
200 => "OK",
301 => "Moved Permanently",
404 => "Not Found",
default => "Unknown status",
};
echo $message;
?>
Relying on strict comparison in match
Each arm uses === semantics. A string "1" does not match the integer 1; be explicit about types.
<?php
$val = "1";
$result = match ($val) {
1 => "int one",
"1" => "string one",
default => "something else",
};
echo $result; // prints "string one"
?>
0, null => … .
Guarding with conditions in match using true
When you need range checks, match against true and let each arm supply a boolean condition.
<?php
$score = 73;
$grade = match (true) {
$score >= 90 => "A",
$score >= 80 => "B",
$score >= 70 => "C",
default => "Try again",
};
echo $grade;
?>
Loops: for, while, do while, foreach
Loops repeat a block while a condition holds or while there are items to process. Choose the loop that reads closest to the problem you are solving.
Counting with for
Use for for indexed counting where you control initialization, condition, and increment.
<?php
for ($i = 0; $i < 3; $i++) {
echo $i . PHP_EOL; // 0, 1, 2
}
?>
Looping until a condition changes with while and do while
while checks before the body runs; do while checks after the body, ensuring the body runs at least once.
<?php
$lines = ["a", "b", "c"];
$i = 0;
while ($i < count($lines)) {
echo $lines[$i] . PHP_EOL;
$i++;
}
$j = 0;
do {
echo "Index: $j" . PHP_EOL;
$j++;
} while ($j < 1);
?>
Iterating collections with foreach
foreach iterates arrays and Traversable objects without manual indexing. Retrieve the value or both key and value.
<?php
$prices = ["apple" => 1.20, "pear" => 1.00];
foreach ($prices as $item => $price) {
echo "$item: $price" . PHP_EOL;
}
?>
| Loop | Best use | Checks |
for | Counting with an index | Before each iteration |
while | Repeat until condition changes | Before each iteration |
do while | Run body at least once | After each iteration |
foreach | Collections and generators | Implicit over items |
foreach (array_values($arr) as …) or collect changes and apply them afterward.
continue, break, and goto
Inside loops you can skip the rest of the current iteration with continue or exit the loop entirely with break. The optional numeric argument targets outer loops. PHP also supports goto with labels, although structured control usually reads better.
Skipping iterations with continue
Use continue to move directly to the next iteration when a condition is not relevant.
<?php
for ($i = 1; $i <= 5; $i++) {
if ($i % 2 === 0) {
continue; // skip even numbers
}
echo $i . " ";
}
// 1 3 5
?>
Exiting loops with break and targeting outer loops
Provide a level to break out of nested loops. A level of 2 exits two loops, and so on.
<?php
for ($i = 0; $i < 3; $i++) {
for ($j = 0; $j < 3; $j++) {
if ($i === 1 && $j === 1) {
break 2; // leave both loops
}
echo "($i,$j) ";
}
}
?>
Understanding when goto harms readability
goto jumps to a label written as label:. Prefer structured loops and functions since they show intent. Reserve goto for simple error handling in very small scripts when refactoring would be disproportionate.
<?php
$ok = false;
if (!$ok) {
goto bail;
}
echo "All good";
bail:
echo "Cleaning up";
?>
return early instead of using goto; early returns are easier to follow.
Declaring and Using switch
A switch statement compares a value against several cases and runs matching blocks until a break is encountered. It is a control statement and does not produce a value.
Writing a basic switch with case and break
Place the target expression after switch, write case labels, and end each handled block with break to prevent falling through.
<?php
$ext = "jpg";
switch ($ext) {
case "jpg":
case "jpeg":
echo "Image";
break;
case "mp3":
echo "Audio";
break;
default:
echo "Unknown";
}
?>
Falling through deliberately and documenting it
When you want a fall through, leave out break deliberately and place a comment that explains why.
<?php
$level = "warning";
switch ($level) {
case "error":
logError();
// fall through
case "warning":
notifyTeam();
break;
}
?>
switch uses loose comparison for case labels in many situations. If you depend on type distinctions, use match or add strict checks inside case blocks.
Pattern-style Branching with match vs switch
match suits value to value selection that returns a result with strict rules; switch suits statement based control with optional fall through. Choose based on whether you need a value or a block-oriented path.
Choosing an expression when you need a value
Use match when the outcome is a value to compute. The expression is concise, exhaustive, and clear about types.
<?php
function taxBand(int $income): string {
return match (true) {
$income < 12570 => "none",
$income < 50270 => "basic",
$income < 125140 => "higher",
default => "additional",
};
}
?>
Choosing a statement when you need side effects
Use switch when you want to run statements that do work such as logging, updating metrics, or calling different functions.
<?php
function handleCommand(string $cmd): void {
switch ($cmd) {
case "start":
startService();
break;
case "stop":
stopService();
break;
default:
echo "Unknown command";
}
}
?>
Comparing behavior at a glance
| Aspect | match | switch |
| Kind | Expression that returns a value | Statement that executes blocks |
| Comparison | Strict === | Often loose; watch types |
| Exhaustiveness | Requires full coverage via default or arms | default optional |
| Fall through | Not allowed | Allowed; requires care |
| Best for | Mapping input to result | Running side effects |
switch that only sets a variable, consider rewriting it as a match expression for clarity.