Skip to main content

🔀 Lesson 5: Control Flow

Programs that run the same code every time aren't very useful. Control flow lets your code make decisions — choosing different paths based on conditions, just like a choose-your-own-adventure book.

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Write conditional logic using if, elseif, and else
  • Use switch statements for multi-branch comparisons
  • Leverage PHP 8's match expression for concise, strict matching
  • Understand truthy and falsy values and how PHP evaluates them
  • Nest and combine conditionals for complex decision-making

Estimated Time: 45 minutes

Prerequisites: Lesson 4 (operators, comparison, logical)

📑 In This Lesson

What Is Control Flow?

📖 Definition

Control Flow: The order in which individual statements and instructions are executed in a program. Control flow structures let you run different blocks of code based on conditions.

By default, PHP executes code from top to bottom, line by line. Control flow structures change this — they create branches where different code runs depending on whether a condition is true or false.

flowchart TD A["Start"] --> B{"Is it raining?"} B -->|Yes| C["Grab umbrella"] B -->|No| D["Wear sunglasses"] C --> E["Go outside"] D --> E E --> F["End"]

PHP gives you three main tools for branching:

Structure Best For Key Feature
if/elseif/else General-purpose conditions Most flexible — any boolean expression
switch Comparing one value against many options Cleaner than long if/elseif chains
match (PHP 8) Same as switch, but stricter and returns a value Strict comparison, no fall-through, is an expression

if / elseif / else

The if statement is the most fundamental control structure in any programming language. It says: "If this condition is true, run this code."

Basic if

<?php
$temperature = 35;

if ($temperature > 30) {
    echo "It's hot outside! Stay hydrated. 🥵";
}
// This only runs if the condition is true

if / else

Add else to handle the case when the condition is not true:

<?php
$hour = 14;

if ($hour < 12) {
    echo "Good morning! ☀️";
} else {
    echo "Good afternoon! 🌤️";
}

if / elseif / else

Chain multiple conditions together with elseif:

<?php
$score = 85;

if ($score >= 90) {
    $grade = "A";
    $message = "Excellent work!";
} elseif ($score >= 80) {
    $grade = "B";
    $message = "Good job!";
} elseif ($score >= 70) {
    $grade = "C";
    $message = "Not bad, keep it up!";
} elseif ($score >= 60) {
    $grade = "D";
    $message = "You passed, but let's improve.";
} else {
    $grade = "F";
    $message = "Let's review the material together.";
}

echo "Grade: $grade — $message";
// Output: Grade: B — Good job!

⚠️ Order Matters!

PHP checks conditions from top to bottom and runs the first block that matches. Once a match is found, all remaining elseif and else blocks are skipped.

// BUG: This always prints "Passing" for any score >= 60
$score = 95;
if ($score >= 60) {
    echo "Passing";     // This matches first!
} elseif ($score >= 90) {
    echo "Excellent";   // Never reached for 95!
}

// FIX: Check the most specific condition first
if ($score >= 90) {
    echo "Excellent";   // Checks this first ✓
} elseif ($score >= 60) {
    echo "Passing";
}

Single-Line Conditions

For simple one-line blocks, you can omit the curly braces — but it's not recommended:

<?php
// This works, but DON'T do it:
if ($loggedIn) echo "Welcome back!";

// This is safer and clearer:
if ($loggedIn) {
    echo "Welcome back!";
}

// Why? Because this common mistake is a silent bug:
if ($loggedIn)
    echo "Welcome back!";
    echo "Loading dashboard..."; // ← ALWAYS runs! Not inside the if!

✅ Best Practice

Always use curly braces with if/elseif/else, even for single-line bodies. It prevents bugs and makes the code easier to modify later.

Real-World Example: User Authentication

<?php
$username = "admin";
$password = "s3cur3P@ss";
$isLocked = false;
$loginAttempts = 2;

if ($isLocked) {
    echo "Account is locked. Contact support.";
} elseif ($loginAttempts >= 5) {
    echo "Too many failed attempts. Account locked.";
    $isLocked = true;
} elseif ($username === "admin" && $password === "s3cur3P@ss") {
    echo "Login successful! Welcome, $username.";
    $loginAttempts = 0;
} else {
    $loginAttempts++;
    echo "Invalid credentials. Attempt $loginAttempts of 5.";
}

Truthy and Falsy Values

When PHP evaluates a condition, it doesn't require a boolean. It can evaluate any value as either truthy or falsy. Understanding this is crucial for writing clean, idiomatic PHP.

📖 Definitions

Falsy: A value that PHP treats as false when used in a boolean context.

Truthy: Any value that is not falsy.

The Complete Falsy List

PHP has exactly eight falsy values. Everything else is truthy.

Falsy Value Type Example
false Boolean if (false)
0 Integer if (0)
0.0 Float if (0.0)
"" (empty string) String if ("")
"0" (string zero) String if ("0")
[] (empty array) Array if ([])
null Null if (null)
-0 Integer if (-0)

⚠️ Surprise: "0" Is Falsy!

The string "0" is falsy in PHP — this trips up many developers, especially those coming from other languages. The string "false" is truthy, however, because it's a non-empty, non-"0" string.

<?php
// Truthy examples — all of these run the if block
if (1)           { echo "truthy\n"; }  // Non-zero integer
if (-1)          { echo "truthy\n"; }  // Negative numbers are truthy!
if (0.5)         { echo "truthy\n"; }  // Non-zero float
if ("hello")     { echo "truthy\n"; }  // Non-empty string
if ("false")     { echo "truthy\n"; }  // ⚠️ The STRING "false" is truthy!
if (" ")         { echo "truthy\n"; }  // Space is a non-empty string
if ([0])         { echo "truthy\n"; }  // Non-empty array

// Falsy examples — none of these run the if block
if (false) { echo "won't run\n"; }
if (0)     { echo "won't run\n"; }
if (0.0)   { echo "won't run\n"; }
if ("")    { echo "won't run\n"; }
if ("0")   { echo "won't run\n"; }  // ⚠️ Watch out!
if ([])    { echo "won't run\n"; }
if (null)  { echo "won't run\n"; }

Using Truthy/Falsy in Practice

<?php
// Checking if a string has content
$name = "Alice";
if ($name) {
    echo "Hello, $name!";   // Runs — $name is truthy
}

// Checking if an array has items
$cart = [];
if ($cart) {
    echo "Your cart has items.";
} else {
    echo "Your cart is empty."; // Runs — empty array is falsy
}

// Checking if a variable exists and has a value
$email = $_POST['email'] ?? null;
if ($email) {
    echo "Processing email: $email";
} else {
    echo "No email provided.";
}

✅ When to Use Truthy/Falsy vs. Strict Comparison

Truthy/falsy checks are great for "does this have a value?" logic. But when the value 0 or "0" is a legitimate input (like a quantity or a database ID), use strict comparisons instead:

// BAD — rejects valid quantity of 0
$quantity = 0;
if ($quantity) { /* never runs for 0! */ }

// GOOD — explicitly check what you mean
if ($quantity !== null) { /* runs for 0 */ }
if (isset($quantity))   { /* runs for 0 */ }

Nested Conditionals

You can place if statements inside other if statements to handle complex logic. But be careful — deep nesting makes code hard to read.

Basic Nesting

<?php
$age = 25;
$hasLicense = true;
$hasCar = false;

if ($age >= 16) {
    if ($hasLicense) {
        if ($hasCar) {
            echo "You can drive your own car!";
        } else {
            echo "You have a license but need a car.";
        }
    } else {
        echo "You're old enough, but need a license first.";
    }
} else {
    echo "You're too young to drive.";
}

The Problem with Deep Nesting

Every level of nesting makes code harder to follow. The example above is already getting difficult — imagine adding more conditions!

flowchart TD A{"age >= 16?"} -->|No| B["Too young"] A -->|Yes| C{"hasLicense?"} C -->|No| D["Need license"] C -->|Yes| E{"hasCar?"} E -->|No| F["Need car"] E -->|Yes| G["Can drive!"]

Flattening with Early Returns / Guard Clauses

A guard clause checks for a disqualifying condition and exits early. This eliminates nesting:

<?php
function canDrive($age, $hasLicense, $hasCar): string
{
    // Guard clauses — check disqualifiers first
    if ($age < 16) {
        return "You're too young to drive.";
    }

    if (!$hasLicense) {
        return "You're old enough, but need a license first.";
    }

    if (!$hasCar) {
        return "You have a license but need a car.";
    }

    // If we reach here, all conditions are met
    return "You can drive your own car!";
}

echo canDrive(25, true, false);
// Output: You have a license but need a car.

✅ Rule of Thumb

If your code nests more than two levels deep, consider refactoring with guard clauses, early returns, or extracting logic into functions. Flat code is almost always easier to understand.

Combining Conditions Instead of Nesting

Sometimes you can flatten nested if statements by combining conditions with &&:

<?php
// Nested (harder to read)
if ($isLoggedIn) {
    if ($isAdmin) {
        if ($hasPermission) {
            deleteUser($userId);
        }
    }
}

// Flat (easier to read, same logic)
if ($isLoggedIn && $isAdmin && $hasPermission) {
    deleteUser($userId);
}

The switch Statement

When you're comparing one value against many possible matches, a switch statement is cleaner than a long if/elseif chain.

Basic Syntax

<?php
$day = "Wednesday";

switch ($day) {
    case "Monday":
        echo "Start of the work week 😩";
        break;
    case "Tuesday":
    case "Wednesday":
    case "Thursday":
        echo "Midweek grind 💪";
        break;
    case "Friday":
        echo "Almost the weekend! 🎉";
        break;
    case "Saturday":
    case "Sunday":
        echo "Weekend! 🏖️";
        break;
    default:
        echo "That's not a valid day.";
        break;
}
// Output: Midweek grind 💪

How switch Works

flowchart TD A["Evaluate expression"] --> B{"Case 1 match?"} B -->|Yes| C["Run case 1 code"] C --> D{"break?"} D -->|Yes| E["Exit switch"] D -->|No| F["Fall through to case 2 code"] B -->|No| G{"Case 2 match?"} G -->|Yes| H["Run case 2 code"] G -->|No| I{"More cases?"} I -->|No| J["Run default"]

⚠️ Don't Forget break!

Without break, PHP falls through to the next case — this is the #1 source of switch bugs:

$fruit = "apple";

switch ($fruit) {
    case "apple":
        echo "Apple\n";     // Runs
        // Missing break!
    case "banana":
        echo "Banana\n";    // ALSO runs! (fall-through)
        break;
    case "cherry":
        echo "Cherry\n";    // Doesn't run (break stopped it)
        break;
}
// Output:
// Apple
// Banana

Intentional Fall-Through

Sometimes fall-through is useful — for grouping multiple cases that share the same code:

<?php
$month = 3; // March

switch ($month) {
    case 12:
    case 1:
    case 2:
        $season = "Winter";
        break;
    case 3:
    case 4:
    case 5:
        $season = "Spring";
        break;
    case 6:
    case 7:
    case 8:
        $season = "Summer";
        break;
    case 9:
    case 10:
    case 11:
        $season = "Fall";
        break;
    default:
        $season = "Unknown";
}

echo "$season"; // Spring

switch Uses Loose Comparison!

A critical detail: switch uses == (loose comparison), not ===. This means type juggling can surprise you:

<?php
$value = 0;

switch ($value) {
    case "hello":
        echo "Matched 'hello'!"; // ⚠️ This runs! 0 == "hello" is true
        break;
    case 0:
        echo "Matched 0";
        break;
}
// Output: Matched 'hello'!
// Because 0 == "hello" → true (string becomes 0 via type juggling)
💡 Tip: If you need strict comparison, use PHP 8's match expression instead — it uses === by default.

Returning from switch

When using switch inside a function, return exits both the case and the function — no break needed:

<?php
function getStatusMessage(string $status): string
{
    switch ($status) {
        case "pending":
            return "Your order is being processed.";
        case "shipped":
            return "Your order is on its way!";
        case "delivered":
            return "Your order has arrived.";
        case "cancelled":
            return "Your order was cancelled.";
        default:
            return "Unknown status: $status";
    }
}

echo getStatusMessage("shipped");
// Output: Your order is on its way!

PHP 8's match Expression

PHP 8 introduced match — a modern alternative to switch that fixes most of its footguns. It's one of the best additions to the language.

Basic Syntax

<?php
$statusCode = 404;

$message = match ($statusCode) {
    200 => "OK",
    301 => "Moved Permanently",
    404 => "Not Found",
    500 => "Internal Server Error",
    default => "Unknown Status",
};

echo $message; // "Not Found"

match vs. switch — Key Differences

Feature switch match
Comparison type == (loose) === (strict) ✅
Fall-through Yes (need break) No fall-through ✅
Returns a value No (it's a statement) Yes (it's an expression) ✅
No match & no default Silently continues Throws UnhandledMatchError
Multiple values per arm Stacked case labels Comma-separated values
Body per arm Multiple statements Single expression only

Strict Comparison (No Type Juggling)

<?php
$value = 0;

// switch would match "hello" (0 == "hello" is true)
// match does NOT — it uses ===
$result = match ($value) {
    "hello" => "Matched hello",
    0       => "Matched zero",   // ✅ This matches (0 === 0)
    default => "No match",
};

echo $result; // "Matched zero"

Multiple Values Per Arm

<?php
$month = 3;

$season = match (true) {
    in_array($month, [12, 1, 2])  => "Winter",
    in_array($month, [3, 4, 5])   => "Spring",
    in_array($month, [6, 7, 8])   => "Summer",
    in_array($month, [9, 10, 11]) => "Fall",
    default                        => "Unknown",
};

echo $season; // "Spring"

// Or more simply, using comma-separated values:
$season = match ($month) {
    12, 1, 2  => "Winter",
    3, 4, 5   => "Spring",
    6, 7, 8   => "Summer",
    9, 10, 11 => "Fall",
    default   => "Unknown",
};

echo $season; // "Spring"

match with Complex Conditions

By matching against true, you can use match like a supercharged if/elseif chain:

<?php
$score = 85;

$grade = match (true) {
    $score >= 90 => "A",
    $score >= 80 => "B",
    $score >= 70 => "C",
    $score >= 60 => "D",
    default      => "F",
};

echo "Grade: $grade"; // "Grade: B"

No match = Error

Unlike switch (which silently does nothing), match throws an error if no arm matches and there's no default:

<?php
$color = "purple";

// This throws UnhandledMatchError!
$hex = match ($color) {
    "red"   => "#FF0000",
    "green" => "#00FF00",
    "blue"  => "#0000FF",
    // No default — "purple" causes an error
};

✅ Pro Tip

Always include a default arm in match unless you want an error for unexpected values. Throwing an error is actually a feature — it catches bugs that switch would silently ignore.

Real-World Example: HTTP Response Handler

<?php
function handleResponse(int $code): string
{
    return match (true) {
        $code >= 200 && $code < 300 => "✅ Success",
        $code >= 300 && $code < 400 => "↪️ Redirect",
        $code === 401                  => "🔒 Unauthorized — please log in",
        $code === 403                  => "🚫 Forbidden — access denied",
        $code === 404                  => "❓ Not Found",
        $code === 429                  => "⏳ Too Many Requests — slow down",
        $code >= 500                   => "💥 Server Error — try again later",
        default                        => "❔ Unknown status code: $code",
    };
}

echo handleResponse(200);  // ✅ Success
echo handleResponse(404);  // ❓ Not Found
echo handleResponse(503);  // 💥 Server Error — try again later

Choosing the Right Tool

Here's a quick decision guide for which control flow structure to use:

flowchart TD A["Need to branch?"] --> B{"How many branches?"} B -->|"1–2"| C["Use if/else"] B -->|"3+"| D{"Comparing one value?"} D -->|No| E["Use if/elseif/else"] D -->|Yes| F{"Need strict comparison?"} F -->|Yes| G["Use match ✅"] F -->|No| H{"Need multiple statements per case?"} H -->|Yes| I["Use switch"] H -->|No| G

Quick Reference

Scenario Use This Why
Simple true/false check if/else Simplest and most readable
Range checks (score >= 90) if/elseif or match(true) Conditions aren't simple value matches
Mapping value to result match Clean, strict, returns a value
Multiple statements per case switch match only allows one expression per arm
One condition, compact Ternary (?:) Covered in Lesson 4!

Alternative Syntax for Templates

When mixing PHP with HTML, PHP offers an alternative colon-based syntax that's easier to read in templates:

<?php $role = "admin"; ?>

<!-- Alternative if/elseif/else syntax -->
<?php if ($role === "admin"): ?>
    <div class="admin-panel">
        <h2>Admin Dashboard</h2>
    </div>
<?php elseif ($role === "editor"): ?>
    <div class="editor-panel">
        <h2>Editor Dashboard</h2>
    </div>
<?php else: ?>
    <div class="user-panel">
        <h2>Welcome</h2>
    </div>
<?php endif; ?>

<!-- Alternative switch syntax -->
<?php switch ($role):
    case "admin": ?>
        <span class="badge-admin">Admin</span>
        <?php break; ?>
    <?php case "editor": ?>
        <span class="badge-editor">Editor</span>
        <?php break; ?>
    <?php default: ?>
        <span class="badge-user">User</span>
<?php endswitch; ?>
💡 Note: The colon syntax (if(): ... endif;) is mainly used when writing HTML templates with embedded PHP. In pure PHP files, always use the standard curly-brace syntax.

Hands-On Exercises

🏋️ Exercise 1: Ticket Pricing

Objective: Use if/elseif/else to calculate admission price based on age and membership status.

Rules:

  1. Children under 5: Free
  2. Children 5–12: $8
  3. Teens 13–17: $12
  4. Adults 18–64: $20
  5. Seniors 65+: $10
  6. Members get 25% off (applied after age pricing)

Starter Code:

<?php
$age = 30;
$isMember = true;

// TODO: Calculate $price based on age
// TODO: Apply 25% member discount if applicable
// TODO: Output the final price
💡 Hint

Set the base price with if/elseif first, then apply the discount in a separate if after. Use number_format() for clean output.

✅ Solution
<?php
$age = 30;
$isMember = true;

// Determine base price by age
if ($age < 5) {
    $price = 0;
} elseif ($age <= 12) {
    $price = 8;
} elseif ($age <= 17) {
    $price = 12;
} elseif ($age <= 64) {
    $price = 20;
} else {
    $price = 10;
}

// Apply member discount
$discount = 0;
if ($isMember && $price > 0) {
    $discount = $price * 0.25;
    $price -= $discount;
}

echo "Age: $age\n";
echo "Member: " . ($isMember ? "Yes" : "No") . "\n";
if ($discount > 0) {
    echo "Discount: -$" . number_format($discount, 2) . "\n";
}
echo "Price: $" . number_format($price, 2) . "\n";
// Output:
// Age: 30
// Member: Yes
// Discount: -$5.00
// Price: $15.00

🏋️ Exercise 2: Day Planner with match

Objective: Use match to create a daily activity planner based on the day of the week.

Instructions:

  1. Create exercise_05b.php
  2. Get the current day using date("l") (returns full day name)
  3. Use match to assign an activity, emoji, and workout
  4. Output a formatted daily plan
💡 Hint

Use date("l") for the day name. You can return arrays or use multiple match calls. Group weekend days with comma-separated values.

✅ Solution
<?php
$today = date("l"); // e.g., "Monday"

$activity = match ($today) {
    "Monday"    => "Team standup & sprint planning",
    "Tuesday"   => "Deep work — no meetings",
    "Wednesday" => "Code review & pair programming",
    "Thursday"  => "Feature development",
    "Friday"    => "Deploy & retrospective",
    "Saturday", "Sunday" => "Rest & personal projects",
};

$workout = match ($today) {
    "Monday", "Thursday"         => "🏋️ Strength training",
    "Tuesday", "Friday"          => "🏃 Cardio",
    "Wednesday"                  => "🧘 Yoga",
    "Saturday", "Sunday"         => "🚶 Walk or rest",
};

$emoji = match ($today) {
    "Monday"    => "🚀",
    "Tuesday"   => "🎯",
    "Wednesday" => "👥",
    "Thursday"  => "💻",
    "Friday"    => "🎉",
    "Saturday", "Sunday" => "😎",
};

echo "===== $emoji $today =====\n";
echo "Activity: $activity\n";
echo "Workout:  $workout\n";

🏋️ Exercise 3: Grade Analyzer (All Three Approaches)

Objective: Write the same grading logic three ways — if/elseif, switch, and match — to see the differences firsthand.

Grading Scale:

90–100 = A, 80–89 = B, 70–79 = C, 60–69 = D, below 60 = F

✅ Solution
<?php
$score = 85;

// === Approach 1: if/elseif ===
if ($score >= 90) {
    $grade1 = "A";
} elseif ($score >= 80) {
    $grade1 = "B";
} elseif ($score >= 70) {
    $grade1 = "C";
} elseif ($score >= 60) {
    $grade1 = "D";
} else {
    $grade1 = "F";
}

// === Approach 2: switch (with intdiv trick) ===
switch (intdiv($score, 10)) {
    case 10:
    case 9:
        $grade2 = "A";
        break;
    case 8:
        $grade2 = "B";
        break;
    case 7:
        $grade2 = "C";
        break;
    case 6:
        $grade2 = "D";
        break;
    default:
        $grade2 = "F";
}

// === Approach 3: match (cleanest!) ===
$grade3 = match (true) {
    $score >= 90 => "A",
    $score >= 80 => "B",
    $score >= 70 => "C",
    $score >= 60 => "D",
    default      => "F",
};

echo "if/elseif: $grade1\n";  // B
echo "switch:    $grade2\n";  // B
echo "match:     $grade3\n";  // B

🎯 Quick Quiz

Question 1: What happens if you forget break in a switch case?

Question 2: Which value is truthy in PHP?

Question 3: What type of comparison does match use?

Question 4: What happens if no arm matches in a match expression and there's no default?

Question 5: What is a "guard clause"?

Summary

🎉 Key Takeaways

  • if/elseif/else — The most flexible control structure; check conditions from most specific to least specific
  • Truthy/Falsy — PHP has 8 falsy values (false, 0, 0.0, "", "0", [], null, -0); everything else is truthy
  • Guard clauses — Check disqualifiers early and return to avoid deep nesting
  • switch — Cleaner than if/elseif for comparing one value against many; uses loose comparison; don't forget break
  • match (PHP 8) — Modern replacement for switch: strict comparison, no fall-through, returns a value, throws on no match
  • Always use curly braces with if/elseif/else to prevent subtle bugs
  • Alternative syntax (if(): ... endif;) exists for HTML templates

📚 Additional Resources

🚀 What's Next?

Now that your code can make decisions, it's time to make it repeat things. In Lesson 6: Loops, you'll master for, while, do-while, and foreach — the tools that let PHP do the heavy lifting.

🎉 Congratulations!

Your code can now make decisions! With if, switch, and match in your toolkit, your programs are no longer one-track — they can adapt to any situation.