PHP Nested if
What Is a Nested if ? In simple terms: A nested if is an if statement placed inside another if (or else ) block. The inner condition is checked only when the outer condition has already been decided.
What Is a Nested if?
In simple terms: A nested if is an if statement placed inside another if (or else) block. The inner condition is checked only when the outer condition has already been decided.
Technically: Any statement can appear inside an if block, including another if. Nesting creates a hierarchy of decisions: the inner decision depends on the outcome of the outer one.
Why Nesting Exists
Some decisions only make sense after an earlier decision:
- Check the PIN only if the card is valid.
- Check the user's role only if they are logged in.
- Apply domestic shipping rates only if the address is in the country.
Nesting expresses this dependency directly. However, deep nesting quickly becomes hard to read, so this lesson also covers how to flatten nested code when it grows.
Syntax
if (outerCondition) {
// runs when outerCondition is true
if (innerCondition) {
// runs when both outerCondition and innerCondition are true
} else {
// runs when outerCondition is true but innerCondition is false
}
} else {
// runs when outerCondition is false
}
Example: Event Entry
<?php
function entryMessage(bool $hasTicket, int $age): string
{
if ($hasTicket) {
if ($age >= 18) {
return "Welcome! Enjoy the event.";
} else {
return "Ticket accepted. Please collect a minor's wristband.";
}
} else {
return "Please buy a ticket at the counter.";
}
}
echo entryMessage(true, 25) . "\n";
echo entryMessage(true, 15) . "\n";
echo entryMessage(false, 30) . "\n";
Output
Welcome! Enjoy the event.
Ticket accepted. Please collect a minor's wristband.
Please buy a ticket at the counter.
How It Works
- The outer
ifchecks whether the visitor has a ticket. - Only for ticket holders does the inner
ifcheck the age, which leads to two different messages. - Visitors without a ticket go to the outer
else; their age is never checked, because it does not matter for them.
This structure shows the dependency clearly: age matters only for ticket holders.
Nested if vs Combined Conditions
When the inner if has no else, and the outer block contains nothing else, the two conditions can usually be combined with &&.
Nested
<?php
$isLoggedIn = true;
$isAdmin = true;
if ($isLoggedIn) {
if ($isAdmin) {
echo "Show admin menu\n";
}
}
Combined
<?php
$isLoggedIn = true;
$isAdmin = true;
if ($isLoggedIn && $isAdmin) {
echo "Show admin menu\n";
}
Output (both)
Show admin menu
The combined version is shorter and flatter. Because && short-circuits, $isAdmin is still checked only when $isLoggedIn is true, so the dependency is preserved.
When Nesting Is the Better Choice
Keep the nesting when the outer block does more than one thing or each level needs its own else:
<?php
$isLoggedIn = true;
$isAdmin = false;
if ($isLoggedIn) {
echo "Show account menu\n";
if ($isAdmin) {
echo "Show admin menu\n";
}
} else {
echo "Show login button\n";
}
Output
Show account menu
Here the outer block always shows the account menu for logged-in users, and the inner if adds the admin menu only for administrators. Combining the conditions would require repeating $isLoggedIn in several places.
The Problem With Deep Nesting
Each level of nesting adds indentation and another condition the reader must keep in mind. Consider an ATM withdrawal:
<?php
function withdraw(bool $cardValid, bool $pinCorrect, float $balance, float $amount): string
{
if ($cardValid) {
if ($pinCorrect) {
if ($amount <= $balance) {
return "Dispensing ₹" . number_format($amount, 2);
} else {
return "Insufficient balance";
}
} else {
return "Incorrect PIN";
}
} else {
return "Card not accepted";
}
}
echo withdraw(true, true, 5000, 2000) . "\n";
echo withdraw(true, true, 1000, 2000) . "\n";
echo withdraw(true, false, 5000, 2000) . "\n";
echo withdraw(false, true, 5000, 2000) . "\n";
Output
Dispensing ₹2,000.00
Insufficient balance
Incorrect PIN
Card not accepted
The code is correct, but:
- The successful path — the one that matters most — is buried three levels deep.
- Each
elseis far away from the condition it belongs to. To find which condition leads to "Card not accepted", you must match braces across the whole function. - Adding another check (for example, a daily withdrawal limit) means another level of nesting.
This shape is sometimes called the arrow anti-pattern, because the indented code forms an arrow pointing to the right.
Flattening With Guard Clauses
A guard clause handles a failure case first and returns immediately. Each check then sits at the top level of the function.
<?php
function withdraw(bool $cardValid, bool $pinCorrect, float $balance, float $amount): string
{
if (!$cardValid) {
return "Card not accepted";
}
if (!$pinCorrect) {
return "Incorrect PIN";
}
if ($amount > $balance) {
return "Insufficient balance";
}
return "Dispensing ₹" . number_format($amount, 2);
}
echo withdraw(true, true, 5000, 2000) . "\n";
echo withdraw(true, true, 1000, 2000) . "\n";
echo withdraw(true, false, 5000, 2000) . "\n";
echo withdraw(false, true, 5000, 2000) . "\n";
Output
Dispensing ₹2,000.00
Insufficient balance
Incorrect PIN
Card not accepted
Why This Is Better
- Each condition is immediately followed by its consequence.
- The checks run in the same order as before, so the behavior is identical.
- The successful path is the last line, at the lowest indentation, easy to find.
- Adding a new check means adding one more guard — no re-indentation of existing code.
Guard clauses work best inside functions, where return can end processing. In loops, continue plays the same role (see Continue Statement).
Flattening With match (true)
When nested if statements only choose a value, a match (true) expression can replace the whole structure.
Nested Version
<?php
function shippingFee(string $country, float $weightKg, float $cartTotal): int
{
if ($country === 'IN') {
if ($weightKg <= 5) {
if ($cartTotal >= 999) {
return 0;
} else {
return 79;
}
} else {
return 149;
}
} else {
if ($weightKg <= 5) {
return 999;
} else {
return 1999;
}
}
}
Flat Version
<?php
function shippingFee(string $country, float $weightKg, float $cartTotal): int
{
return match (true) {
$country !== 'IN' => $weightKg <= 5 ? 999 : 1999,
$weightKg > 5 => 149,
$cartTotal >= 999 => 0,
default => 79,
};
}
$tests = [
['IN', 2, 1200],
['IN', 2, 500],
['IN', 8, 1200],
['US', 2, 500],
['US', 8, 500],
];
foreach ($tests as [$country, $weight, $total]) {
echo "$country, {$weight} kg, ₹$total → ₹" . shippingFee($country, $weight, $total) . "\n";
}
Output
IN, 2 kg, ₹1200 → ₹0
IN, 2 kg, ₹500 → ₹79
IN, 8 kg, ₹1200 → ₹149
US, 2 kg, ₹500 → ₹999
US, 8 kg, ₹500 → ₹1999
How the Flat Version Works
match (true) checks each condition from top to bottom and returns the value of the first one that is true:
- International orders are handled first, with a small ternary for the weight.
- For domestic orders, heavy parcels cost ₹149, whatever the cart total.
- Light domestic parcels are free above ₹999.
- Everything else costs ₹79.
The order of the arms encodes the same logic as the nesting, but the full rule table is visible at a glance. match is covered in PHP Match.
Nesting in Loops and Templates
Nested if statements also appear inside loops and HTML templates. The same advice applies: keep nesting shallow, and in loops, use continue to skip items early.
<?php
$orders = [
['id' => 1, 'paid' => true, 'items' => 3],
['id' => 2, 'paid' => false, 'items' => 1],
['id' => 3, 'paid' => true, 'items' => 0],
];
foreach ($orders as $order) {
if (!$order['paid']) {
echo "Order {$order['id']}: waiting for payment\n";
continue;
}
if ($order['items'] === 0) {
echo "Order {$order['id']}: empty order, skipped\n";
continue;
}
echo "Order {$order['id']}: ready to pack {$order['items']} items\n";
}
Output
Order 1: ready to pack 3 items
Order 2: waiting for payment
Order 3: empty order, skipped
Each special case is handled and skipped with continue, so the main processing line is not nested at all.
Common Mistakes
Mismatched else Without Braces
Effect: An else belongs to the nearest if, which may not be the one its indentation suggests (the "dangling else").
Fix: Always use braces at every level.
Deep Nesting
Effect: Code that is hard to read, review, and extend.
Fix: Use guard clauses, combine conditions with &&, or use match (true). As a rule of thumb, more than two or three levels is a signal to refactor.
Repeating the Same Check at Several Levels
Effect: Duplicated conditions that can drift apart when one copy is changed.
Fix: Check each condition once, and store results in named variables if they are reused.
Nesting Independent Checks
Effect: A check is skipped because it was placed inside an unrelated condition.
Fix: Nest only when the inner check truly depends on the outer one; otherwise, use separate if statements.
Best Practices
- Nest only when an inner decision depends on an outer one and each level has its own meaningful outcome.
- Combine simple dependent conditions with
&&when there is noelseat the inner level. - In functions, prefer guard clauses that return early; in loops, use
continue. - Use
match (true)when nested conditions only select a value. - Always use braces, and keep nesting to two or three levels at most.
Official reference: https://www.php.net/manual/en/control-structures.if.php