PHP Zero to Advanced #3: Conditionals, Loops & the match Expression
So far our scripts have run from top to bottom, one line after another. Real programs need to make decisions ("is this user logged in?") and repeat work ("send an email to every subscriber"). In this part you will learn PHP's control structures: if, switch, the modern match expression, and all the loop types.
if, elseif, and else
The if statement runs a block only when its condition is truthy. Chain extra conditions with elseif and catch everything else with else. Conditions are checked from top to bottom, and only the first matching block runs.
<?php
$score = 82;
if ($score >= 90) {
$grade = "A";
} elseif ($score >= 80) {
$grade = "B";
} elseif ($score >= 70) {
$grade = "C";
} else {
$grade = "D";
}
echo "Score $score gets grade $grade\n";
$age = 17;
$hasPermission = true;
if ($age >= 18 || $hasPermission) {
echo "Access granted\n";
}
Output:
Score 82 gets grade B
Access granted
Combine conditions with && (and), || (or), and ! (not). Always use curly braces, even for one-line blocks; it prevents bugs when you add more lines later.
Ternary, short ternary, and null coalescing
For simple "pick one of two values" decisions, the inline operators are shorter and still readable:
<?php
$stock = 0;
echo $stock > 0 ? "In stock" : "Sold out", "\n";
$nickname = "";
echo $nickname ?: "Anonymous", "\n"; // short ternary: falsy check
$settings = ['theme' => null];
echo $settings['theme'] ?? "light", "\n"; // null coalescing: null/undefined check
echo $settings['lang'] ?? "en", "\n";
Output:
Sold out
Anonymous
light
en
The difference matters: ?: falls back on any falsy value (including "" and 0), while ?? only falls back on null or a missing key, and it never raises a warning for undefined keys.
switch
switch compares one value against many cases. Each case needs a break, otherwise execution "falls through" to the next case. Sometimes fall-through is intentional, as with the weekend example below.
<?php
$day = "sat";
switch ($day) {
case "sat":
case "sun":
echo "Weekend\n";
break;
case "fri":
echo "Almost weekend\n";
break;
default:
echo "Weekday\n";
}
Output:
Weekend
switch uses loose comparison (==), which is one reason modern PHP code often prefers match.
The match Expression (PHP 8)
match is like a safer, more compact switch:
- It is an expression, so it returns a value you can assign or echo.
- It uses strict comparison (
===). - There is no fall-through and no
breakneeded. - Multiple values can share one arm, separated by commas.
<?php
$status = 404;
$message = match ($status) {
200, 201 => "Success",
301, 302 => "Redirect",
404 => "Not Found",
500 => "Server Error",
default => "Unknown",
};
echo $message . "\n";
$score = 82;
$grade = match (true) {
$score >= 90 => "A",
$score >= 80 => "B",
$score >= 70 => "C",
default => "D",
};
echo "Grade: $grade\n";
Output:
Not Found
Grade: B
The match (true) pattern is a neat replacement for long if/elseif chains that only assign a value. Because match is strict, 1 and "1" are different, and if no arm matches and there is no default, PHP throws an UnhandledMatchError:
<?php
$value = "1";
echo match ($value) {
1 => "Integer one",
"1" => "String one",
};
echo "\n";
try {
echo match ("2") {
"1" => "One",
};
} catch (\UnhandledMatchError $e) {
echo get_class($e) . ": " . $e->getMessage() . "\n";
}
Output:
String one
UnhandledMatchError: Unhandled match case '2'
We will cover try/catch properly in Part 8. For now, remember: add a default arm unless you really want an error for unexpected values.
Loops
for
Use for when you know how many times to loop. It has three parts: initialization, condition, and step.
<?php
for ($i = 1; $i <= 5; $i++) {
echo "Row $i\n";
}
for ($i = 10; $i > 0; $i -= 3) {
echo $i . " ";
}
echo "\n";
Output:
Row 1
Row 2
Row 3
Row 4
Row 5
10 7 4 1
while and do-while
while keeps going as long as the condition is true, which is ideal when you do not know the number of iterations in advance. do-while checks the condition after the body, so it always runs at least once.
<?php
$balance = 1000;
$months = 0;
while ($balance < 1500) {
$balance *= 1.1; // 10% growth per month
$months++;
}
echo "Target reached after $months months: " . round($balance, 2) . "\n";
$attempt = 0;
do {
$attempt++;
echo "Attempt #$attempt\n";
} while ($attempt < 3);
Output:
Target reached after 5 months: 1610.51
Attempt #1
Attempt #2
Attempt #3
foreach
foreach is the loop you will use most in PHP, because it iterates over arrays directly. You can get just the values, or both the keys and the values.
<?php
$fruits = ["apple", "banana", "mango"];
foreach ($fruits as $fruit) {
echo "- $fruit\n";
}
$prices = ["apple" => 5000, "banana" => 3000, "mango" => 12000];
foreach ($prices as $name => $price) {
echo str_pad($name, 8) . number_format($price, 0, ',', '.') . "\n";
}
Output:
- apple
- banana
- mango
apple 5.000
banana 3.000
mango 12.000
break and continue
continue skips to the next iteration, and break exits the loop completely.
<?php
for ($i = 1; $i <= 10; $i++) {
if ($i % 2 === 0) {
continue; // skip even numbers
}
if ($i > 7) {
break; // stop the loop entirely
}
echo $i . " ";
}
echo "\n";
Output:
1 3 5 7
Loops can also be nested. Here is a small multiplication table:
<?php
for ($row = 1; $row <= 3; $row++) {
for ($col = 1; $col <= 3; $col++) {
echo str_pad($row * $col, 4, " ", STR_PAD_LEFT);
}
echo "\n";
}
Output:
1 2 3
2 4 6
3 6 9
Common Mistakes
- Using
=instead of==/===in a condition.if ($role = "admin")assigns the value and is always true. - Forgetting
breakinswitch. The next case runs too. Prefermatchwhen you are mapping values. - Infinite loops. Make sure the
whilecondition eventually becomes false, for example by updating the counter. - Foreach by reference without
unset. Afterforeach ($arr as &$v),$vstill points to the last element. A later loop using$vsilently overwrites it:
<?php
$numbers = [1, 2, 3];
foreach ($numbers as &$n) {
$n *= 2;
}
// unset($n); <-- forgetting this causes the bug below
foreach ($numbers as $n) {
}
print_r($numbers);
Output:
Array
(
[0] => 2
[1] => 4
[2] => 4
)
The last element became 4 instead of 6. Always call unset($n) right after a by-reference loop, or avoid references and build a new array instead.
Exercise: FizzBuzz
Print the numbers 1 to 15. For multiples of 3 print "Fizz", for multiples of 5 print "Buzz", and for multiples of both print "FizzBuzz". Try it yourself first, then compare with this match (true) solution:
<?php
for ($i = 1; $i <= 15; $i++) {
echo match (true) {
$i % 15 === 0 => "FizzBuzz",
$i % 3 === 0 => "Fizz",
$i % 5 === 0 => "Buzz",
default => $i,
}, "\n";
}
Output:
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
Notice that the order of the arms matters: the % 15 check must come first, because match stops at the first arm that matches.
Summary
if/elseif/elsehandles general branching; ternary and??handle quick value choices.switchuses loose comparison and needsbreak;matchis strict, returns a value, and has no fall-through.- Use
forfor counted loops,while/do-whilefor condition-based loops, andforeachfor arrays. breakexits a loop,continueskips to the next iteration.
In Part 4 we will organize code into reusable functions with type declarations, default parameters, arrow functions, and a clear understanding of variable scope.
Related articles
PostgreSQL Zero to Advanced #1: Introduction & Setup
Start the series by understanding what a relational database is, installing PostgreSQL locally, and getting comfortable with psql commands like \l, \c and \dt.
Ruby on Rails Zero to Advanced #1: Ruby Basics
Start the Rails journey with the language underneath it: irb, variables, strings, puts vs p, and symbols explained with runnable examples.
Python Zero to Advanced #1: Introduction & Setup
Start your Python journey: what Python is, how to install it, how to use the REPL, print your first output, and run .py files from the terminal.