PHP Control Flow

Branch your code with if/elseif/else, switch, and the modern PHP 8 match expression. Learn truthiness, guard clauses, and which construct fits which situation.

Beginner 8 min read 10 examples

if / elseif / else

PHP
<?php
$score = 78;

if ($score >= 90) {
    echo "A";
} elseif ($score >= 75) {
    echo "B";
} elseif ($score >= 60) {
    echo "C";
} else {
    echo "F";
}

// Single statement - braces optional but recommended
if ($score >= 60) echo "Pass";

// Multiple conditions with && and ||
if ($age >= 18 && $hasLicense) {
    echo "Can drive";
}
Always use braces

Even for one-line ifs, write the braces. It prevents the classic "adding a second line accidentally breaks the condition" bug and matches modern PHP coding style (PSR-12).

Truthy & Falsy Values

Any value can be used as a condition. PHP coerces it to bool:

PHP
<?php
// FALSY values
if (false)   {} // skip
if (null)    {} // skip
if (0)       {} // skip
if (0.0)     {} // skip
if ("")      {} // skip
if ("0")     {} // skip   <-- surprising
if ([])      {} // skip

// TRUTHY - everything else
if ("0.0")   {}   // run!
if ("false") {}   // run!
if (-1)      {}   // run
if (" ")     {}   // run (string with a space)

switch Statement

PHP
<?php
$role = "editor";

switch ($role) {
    case "admin":
        echo "Full access";
        break;

    case "editor":
    case "author":            // multiple cases share code
        echo "Can edit";
        break;

    case "viewer":
        echo "Read only";
        break;

    default:
        echo "Unknown role";
}
switch uses == (loose) comparison

switch (1) matches case "1". This bites when you switch on user input. PHP 8's match uses strict === - prefer it.

match Expression (PHP 8.0+)

The modern replacement for switch - shorter, safer, and returns a value:

PHP
<?php
$role = "editor";

$access = match ($role) {
    "admin"            => "full",
    "editor", "author" => "edit",   // multiple values per arm
    "viewer"           => "read",
    default            => "none",
};

echo $access;       // edit

// match can run expressions on the right
$discount = match (true) {
    $total >= 1000 => 0.20,
    $total >= 500  => 0.10,
    $total >= 100  => 0.05,
    default        => 0,
};
Featureswitchmatch
ComparisonLoose ==Strict ===
Returns valueNoYes
Fall-throughYes (needs break)No
Default behavior on missSkip silentlyThrow UnhandledMatchError
Multiple values per armStacked casesComma-separated

Ternary & Null Coalesce

For tiny if-else expressions, use the ternary or null coalesce instead:

PHP
<?php
// Standard ternary
$status = $active ? "online" : "offline";

// Short ternary (Elvis) - falsy fallback
$name = $input ?: "Guest";

// Null coalescing - missing-key fallback
$lang = $_GET["lang"] ?? "en";

// Chained
$value = $primary ?? $secondary ?? $default;

Guard Clauses

Return early on bad input to avoid pyramid-of-doom nesting:

PHP
<?php
// BAD - deep nesting
function chargeUser($user, $amount) {
    if ($user !== null) {
        if ($user->isActive()) {
            if ($amount > 0) {
                // real work hidden 3 levels deep
                return $user->charge($amount);
            }
        }
    }
    return false;
}

// GOOD - guard clauses
function chargeUser($user, $amount) {
    if ($user === null)        return false;
    if (!$user->isActive())    return false;
    if ($amount <= 0)          return false;

    return $user->charge($amount);
}

Alternative Syntax for Templates

Inside HTML, replace braces with : and end*:

PHP
<?php if ($user->isAdmin()): ?>
    <a href="/admin">Admin Panel</a>
<?php elseif ($user->isEditor()): ?>
    <a href="/editor">Editor</a>
<?php else: ?>
    <a href="/login">Login</a>
<?php endif; ?>

Next Steps

Frequently Asked Questions

match (PHP 8+) uses strict comparison (===), has no fall-through, returns a value, and throws on unmatched. switch uses loose comparison (==), needs break, and doesn't return. Prefer match for new code.

Use elseif for a sequence of mutually exclusive conditions. Use nested if only when the inner condition genuinely depends on the outer one. For more than 3-4 branches, consider match or a lookup array.

Functionally identical for normal syntax. But the alternative if: ... endif; template syntax requires the single word elseif. Always use elseif to avoid that trap.

Use guard clauses: return early on invalid input at the top of the function so the happy path stays flat. Combine with early continue or break in loops.

Yes, but if no arm matches it throws UnhandledMatchError. Adding default => ... handles the fall-through case explicitly. Treat it like a typed exhaustive switch.