CodeWalkers is in beta

PHP Conditionals and Control Flow

Published Updated

PHP control flow decides which statements run and how often they repeat. Conditionals choose a branch, while loops repeat a block until the data or condition says to stop.

Picture a parcel-sorting desk handling each incoming request. Each parcel arrives with labels, the comparison reads those labels, and the branch sends it to one lane. A loop keeps taking the next parcel until the queue is empty. Clear conditions make the sorting rule visible to both the runtime and the next developer.

Use strict comparisons for application decisions unless type conversion is deliberate. Keep branches large enough to read, stop switch cases explicitly, and make every loop's exit condition easy to identify.

Compare Before You Branch

A conditional starts with an expression that PHP evaluates as true or false. The expression may compare values, read a boolean, call a function, or combine conditions with && and ||.

<?php

$age = 19;
$country = "AU";

if ($age >= 18 && $country === "AU") {
    echo "Adult account";
}

The parcel reaches the adult-account lane only when both labels match. Strict identity operators check type and value, while loose equality may convert types first:

<?php

$input = "0";

var_dump($input == 0);  // true
var_dump($input === 0); // false

Submitted form input normally reaches PHP as strings. Validate and convert it before the branch so the condition compares the application value you intended:

<?php

$quantity = filter_input(
    INPUT_POST,
    "quantity",
    FILTER_VALIDATE_INT,
);

if ($quantity === null || $quantity === false || $quantity < 1) {
    throw new InvalidArgumentException(
        "Quantity must be a positive integer",
    );
}

Build a Conditional Chain Step by Step

Start with one branch for the invalid state. This version rejects an empty order and leaves every positive total on the normal path:

<?php

if ($orderTotalCents <= 0) {
    $status = "empty";
}

Add elseif when another condition needs its own lane, then use else for the remaining valid values:

<?php

if ($orderTotalCents <= 0) {
    $status = "empty";
} elseif ($orderTotalCents < 5000) {
    $status = "small";
} else {
    $status = "standard";
}

The order of conditions matters because PHP stops after the first true branch. Put the narrow invalid or exceptional cases first, then let the ordinary path continue.

Guard clauses use the same pattern at a function boundary:

<?php

function publishArticle(array $article, int $userId): void
{
    if (! userCanPublish($userId)) {
        throw new RuntimeException(
            "User cannot publish articles",
        );
    }

    if (($article["title"] ?? "") === "") {
        throw new InvalidArgumentException(
            "Article title is required",
        );
    }

    markArticlePublished($article);
}

The permission and title failures leave early, so the final write remains on a shallow, visible path. This shape helps reviewers confirm that authorization happens before the data change.

Use Match for Value Decisions

A match expression, available since PHP 8.0, sends one subject value to a strictly compared arm and returns that arm's result. It suits a sorting desk with a known set of exact labels:

<?php

$label = match ($status) {
    "draft" => "Draft",
    "review" => "In review",
    "published" => "Published",
    default => "Unknown",
};

Change the returned values without changing the branch shape when another part of the application needs the same status set:

<?php

$responseCode = match ($status) {
    "draft" => 200,
    "review" => 202,
    "published" => 201,
    default => 500,
};

match uses ===, returns a value, never falls through, and must be exhaustive. If the set is meant to be closed, omitting default lets an unexpected state throw UnhandledMatchError during testing instead of hiding it under a generic label.

Use Switch for Statement Branches

switch remains useful when one value selects a block of statements rather than one returned value:

<?php

switch ($requestMethod) {
    case "GET":
        showArticle($articleId);
        break;

    case "POST":
        updateArticle($articleId);
        break;

    default:
        http_response_code(405);
        echo "Method not allowed";
        break;
}

switch compares loosely and continues into the next case unless break, return, or throw stops it. Deliberate fallthrough can group labels, but a short comment should make the shared lane explicit.

Repeat Work with Loops

Loops keep the sorting desk moving through its queue. Choose the loop from the data you have and the condition that ends the repetition.

Loop over an Array with Foreach

Start with the values when the array keys do not matter:

<?php

$statuses = ["draft", "review", "published"];

foreach ($statuses as $status) {
    echo $status, PHP_EOL;
}

The loop prints each status on its own line:

draft
review
published

Add the key when the output needs both the position and the value:

<?php

foreach ($statuses as $position => $status) {
    echo $position + 1, ": ", $status, PHP_EOL;
}

foreach advances through the array for you. Use a reference only when the loop must modify the original values, then call unset() on the reference variable after the loop to avoid carrying it into later code.

Repeat Until a Condition Changes

A while loop fits a queue whose length changes as each item is handled:

<?php

$jobs = ["resize-image", "send-email", "build-report"];

while ($jobs !== []) {
    $job = array_shift($jobs);
    echo "Processing: ", $job, PHP_EOL;
}

Each pass removes one job, so the queue eventually becomes empty and the condition becomes false. When the loop body cannot change the exit condition, the code can run until a timeout or manual stop.

Place Branches at Application Boundaries

The most important PHP branches sit where data changes meaning or authority: request input becomes a validated value, a user identity becomes an authorization decision, a form becomes a database write, or a database result becomes HTML.

PHP security fundamentals uses branches around sessions, CSRF, uploads, and output. PHP and MySQL with PDO branches around query results and failed writes. Building a small PHP database app connects those checks across a complete request.

Common Pitfalls & Debugging

Loose Comparison Accepts the Wrong Value

Symptom: a string from a form matches a numeric condition unexpectedly. Cause: == converted one operand before comparing. Fix: validate and convert the input first, then use === or another strict comparison against the expected type.

Switch and Match Handle Types Differently

Symptom: code moved from switch to match stops recognizing a value such as string "1" against integer 1. Cause: switch uses loose equality while match uses identity. Fix: normalize the subject to one documented type before branching and cover every expected arm.

A Loop Never Reaches Its Exit

Symptom: a request hangs or reaches its execution-time limit. Cause: the loop body never changes the condition that keeps the loop running. Fix: identify the exit condition in plain language, update it on every intended path, and add a bounded counter when external data can keep arriving.

Frequently Asked Questions

When should you use if instead of match?

Use if when branches depend on different boolean expressions, ranges, permissions, validation results, or several values at once. Use match when one subject value must select and return a result from a known set of strictly compared alternatives.

Can match be used without a subject, like an if ladder?

Yes. Passing true as the subject makes each arm a boolean test, which gives an if and elseif chain match's exhaustiveness and its lack of fallthrough. It reads well when every branch tests a different condition.

Does match evaluate every arm or stop at the first?

It stops at the first arm whose condition equals the subject, working in the order written, exactly as an if and elseif chain short-circuits. Arms below the match are never evaluated.

Which loop should you use for a PHP array?

Use foreach for most arrays because it gives each key and value directly without a separate counter. Use for when the numeric position controls the work, and use while when repetition should continue until a condition changes.

Does a foreach loop by reference leave anything behind that can cause bugs after it finishes?

Yes. The loop variable stays bound by reference to the array's last element after the loop ends. Reusing that name in a later foreach silently overwrites what looks like a separate element, corrupting data instead of raising an error. Call unset() on the reference variable right after the loop.

Sources

  1. [1]
  2. [2]
    PHP if
    (php.net)
  3. [3]
    PHP switch
    (php.net)
  4. [4]
    PHP match
    (php.net)
  5. [5]
    PHP foreach
    (php.net)
  6. [6]
    PHP while
    (php.net)
  7. [7]