PHP from the ground up

Decisions: if, and the equality trap

Branching with if and elseif, choosing between two values with a ternary, the match expression, and the one comparison rule that saves the most debugging time.

Part 5 of 13

Beginner15 min readBack to Courses

If, elseif, else

A condition is an expression that comes out true or false, and if runs the block after it when it does: if ($score >= 90) { … } elseif ($score >= 80) { … } else { … }. The chain is read top to bottom, and the first true branch wins — order is part of the meaning.

A ternary chooses between two values rather than running blocks: $label = $score >= 50 ? "pass" : "fail";. When the two outcomes are values and the condition is short, a ternary reads better than an if; when either side grows, the if is the kinder form.

Braces are optional for a single statement, and they are still worth writing. Without them, adding a second line to a branch silently moves it outside the if, and an else can attach to the wrong branch. The brace is not decoration; it is the boundary of the block, and it is the first thing a reader looks for when a condition seems to do nothing.

Reading a condition out loud

A condition is easier to trust when you can say it as a sentence. $age >= 18 && $hasTicket reads “old enough and has a ticket”; !$blocked reads “not blocked”. When a condition needs the word “unless” twice, invert it: if (!$blocked && !$expired) is usually clearer written as $active = !$blocked && !$expired; and then if ($active).

The comparison itself should put the thing that varies on one side and the fixed value on the other. if ($status === "paid") is easier to read than if ("paid" === $status), though the reversed form has one advantage: an accidental = becomes a parse error instead of an assignment. Modern editors warn about the assignment either way, so readability usually wins.

Two equals signs, or three

== compares after converting the types to fit, and === compares value and type together. 0 == "0" is true; 0 === "0" is false. "1" == "01" is true, which surprises everyone the first time: both look like the number one, so the conversion says yes.

The rule that prevents most of this: use === for everything, unless you can say precisely why loose comparison is what you want. A function that returns a number or false is the honest exception — there the types genuinely are different answers — and even then, === false is the clearer way to say it.

Match: a chain that returns

match is the modern alternative to a long if chain when every branch produces a value. It compares with the same rules as ===, takes expressions rather than blocks, and — unlike a switch — throws an error when nothing matches and there is no default. A silent fallthrough is impossible, which is the whole point.

The pattern that makes it shine with ranges is match (true): each arm is a condition that is either true or not, and the first true arm wins. It reads like a table, and with strict comparison underneath, it behaves like one.

A grade, and a comparison clinic

<?php
$score = 87;

if ($score >= 90) {
    $grade = 'A';
} elseif ($score >= 80) {
    $grade = 'B';
} else {
    $grade = 'C';
}
echo "Grade: $grade" . PHP_EOL;

// The same shape as a ternary: two values, one condition.
echo ($score >= 80 ? 'pass' : 'fail') . PHP_EOL;

// match (true) reads like a table of conditions.
$label = match (true) {
    $score >= 90 => 'excellent',
    $score >= 80 => 'good',
    default      => 'keep going',
};
echo "Label: $label" . PHP_EOL;

// Loose comparison converts; strict comparison does not.
var_dump(0 == '0');      // true
var_dump(0 === '0');     // false
var_dump('1' == '01');   // true — both convert to 1
var_dump('abc' == 0);    // false in PHP 8

A delivery cost calculator

Run it and read which branch each parcel takes. Then add a same-day option that costs 9.99 regardless of weight, and give the match a new arm for parcels over 30 kg.

Output
Click "Run" to execute your code

An empty string, the string "0", the number 0 and null are all falsy; almost everything else is truthy — including the string "false". Whenever a condition depends on that list, compare explicitly instead of leaning on it.

Truthiness, and the short circuit

An if does not require a boolean; it converts whatever it is given. 0, 0.0, "0", "", null, false and an empty array are false, and almost everything else is true — including the string "false". That list is the reason a condition should compare explicitly when the value could be one of the surprising ones.

&& and || stop as soon as the answer is known. In $user !== null && $user->isActive(), the method is only called when the left side is true, which is what makes the guard safe. match is stricter than a chain of comparisons: it uses ===, every arm is an expression, and a value with no arm and no default throws instead of falling through.

Three decision mistakes are common: writing if ($count = 0) when the comparison was meant, comparing a value that may be 0 or "0" with ==, and leaving the default off a match whose input can be anything. Use ===, put the constant on the left if assignment is a habit, and make the unmatched case explicit.

Try these next

  • Write a grade chain with if, elseif and else, then test the exact boundaries 80 and 90.
  • Rewrite the same chain as match (true) and add a default arm.
  • Print var_dump() for 0 == "0", 0 === "0" and "1" == "01", and say which result surprised you.

A shipping desk

Run it for Ireland, then change the country to `DE` and the express flag to `true`. Add a same-day option that costs 12.00 regardless of zone.

Output
Click "Run" to execute your code

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

Which comparison should be the default in PHP?