PHP If Operators
An if statement is only as reliable as its condition. This lesson focuses on writing conditions : which operators and functions to use for numbers, strings, missing values, types, and lists, and how t
An if statement is only as reliable as its condition. This lesson focuses on writing conditions: which operators and functions to use for numbers, strings, missing values, types, and lists, and how to combine them correctly. Most real bugs in if statements are not in the if itself but in a condition that does not mean exactly what the author intended.
Operators Used in Conditions
Comparison Operators
| Operator | Meaning | Example | true When |
|---|---|---|---|
=== | Identical | $status === 'paid' | Same value and type |
!== | Not identical | $code !== '' | Different value or type |
== | Equal (with type juggling) | $qty == '5' | Equal after conversion |
!= / <> | Not equal (with type juggling) | $a != $b | Not equal after conversion |
> | Greater than | $stock > 0 | Left is larger |
< | Less than | $age < 18 | Left is smaller |
>= | Greater than or equal | $score >= 40 | Left is larger or equal |
<= | Less than or equal | $items <= 10 | Left is smaller or equal |
Logical Operators
| Operator | Meaning | Example |
|---|---|---|
&& | Both must be true | $age >= 18 && $hasId |
|| | At least one must be true | $isAdmin || $isOwner |
! | Reverses a condition | !$isBlocked |
Details are in Comparison Operators and Logical Operators. This lesson applies them to common real-world checks.
Checking Numbers and Ranges
Basic Comparisons
<?php
$stock = 4;
if ($stock === 0) {
echo "Out of stock\n";
} elseif ($stock < 5) {
echo "Only $stock left\n";
} else {
echo "In stock\n";
}
Output
Only 4 left
Ranges
PHP has no "between" operator. Combine two comparisons with &&, and decide for each boundary whether it is inclusive (<=, >=) or exclusive (<, >):
<?php
function isBetween(int|float $value, int|float $min, int|float $max): bool
{
return $value >= $min && $value <= $max;
}
$hour = 18;
if (isBetween($hour, 9, 17)) {
echo "Support is online\n";
} else {
echo "Support is offline (hours: 9–17)\n";
}
var_dump(isBetween(17, 9, 17));
var_dump(isBetween(17.5, 9, 17));
Output
Support is offline (hours: 9–17)
bool(true)
bool(false)
Testing the boundary values (here 17) is the quickest way to catch off-by-one mistakes. A helper function with a clear name also documents the rule.
Numbers From Input
Input from forms and URLs is always a string. Convert and validate before comparing:
<?php
$input = '25abc';
$qty = filter_var($input, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1, 'max_range' => 99]]);
if ($qty === false) {
echo "Quantity must be a whole number from 1 to 99\n";
} else {
echo "Adding $qty items\n";
}
Output
Quantity must be a whole number from 1 to 99
filter_var() returns an int or false. Comparing with === false matters because a valid result could be falsy in other situations (for example, 0 when zero is allowed).
Checking Strings
Exact Match
<?php
$status = 'shipped';
if ($status === 'shipped' || $status === 'delivered') {
echo "Tracking available\n";
}
Output
Tracking available
Case-Insensitive Match
<?php
$role = 'Admin';
if (strcasecmp($role, 'admin') === 0) {
echo "Admin access\n";
}
if (mb_strtolower($role) === 'admin') {
echo "Admin access (normalized)\n";
}
Output
Admin access
Admin access (normalized)
strcasecmp() returns 0 when the strings are equal ignoring case. Normalizing with mb_strtolower() also works for non-English text. Even better, normalize values once when you receive them, and compare normalized values everywhere else.
Contains, Starts With, Ends With (PHP 8.0+)
<?php
$url = 'https://gocourse.in/php/if-operators';
$file = 'report.PDF';
if (str_starts_with($url, 'https://')) {
echo "Secure URL\n";
}
if (str_contains($url, '/php/')) {
echo "PHP section\n";
}
if (str_ends_with(strtolower($file), '.pdf')) {
echo "PDF document\n";
}
Output
Secure URL
PHP section
PDF document
These functions return real booleans, so they can be used directly as conditions — unlike strpos(), which returns 0 for a match at the start (see Search in Strings).
Empty Strings and Whitespace
<?php
$comment = " ";
if ($comment === '') {
echo "Exactly empty\n";
}
if (trim($comment) === '') {
echo "Empty or only whitespace\n";
}
Output
Empty or only whitespace
A field containing only spaces is not ''. Trim user input before checking whether it is empty.
Checking for Missing or Empty Values
PHP offers several tools that look similar but answer different questions:
| Check | Question It Answers |
|---|---|
isset($x) | Does $x exist and is it not null? |
$x === null | Is the value exactly null? (Warning if $x does not exist.) |
empty($x) | Is $x missing or falsy ('', '0', 0, 0.0, null, false, [])? |
$x === '' | Is the value exactly an empty string? |
array_key_exists('k', $arr) | Does the key exist, even if its value is null? |
count($arr) > 0 / $arr !== [] | Does the array contain any elements? |
How They Behave
| Value | isset() | empty() | === null | === '' |
|---|---|---|---|---|
| (undefined) | false | true | Warning, then true | Warning, then false |
null | false | true | true | false |
'' | true | true | false | true |
'0' | true | true | false | false |
0 | true | true | false | false |
' ' (spaces) | true | false | false | false |
[] | true | true | false | false |
'PHP' | true | false | false | false |
The empty() Trap
empty() treats '0' and 0 as empty. That is wrong when zero is a meaningful answer:
<?php
$form = ['children' => '0', 'nickname' => ''];
if (empty($form['children'])) {
echo "empty(): children is missing\n";
}
if (!isset($form['children']) || trim($form['children']) === '') {
echo "explicit: children is missing\n";
} else {
echo "explicit: children = {$form['children']}\n";
}
Output
empty(): children is missing
explicit: children = 0
The user answered "0 children", but empty() reports the field as missing. The explicit check treats only a missing key or a blank value as missing. Prefer explicit checks for user input; use empty() only when every falsy value really should count as empty.
Optional Values With ??
<?php
$query = [];
if (($query['debug'] ?? '') === '1') {
echo "Debug mode\n";
} else {
echo "Normal mode\n";
}
Output
Normal mode
?? supplies a default when the key is missing, which avoids "Undefined array key" warnings without a separate isset() call.
Checking Types
<?php
$values = [42, '42', 4.2, null, ['a'], new DateTime()];
foreach ($values as $value) {
$type = get_debug_type($value);
if (is_int($value)) {
echo "$type: integer\n";
} elseif (is_numeric($value)) {
echo "$type: numeric (int-like or float-like)\n";
} elseif (is_array($value)) {
echo "$type: array with " . count($value) . " element(s)\n";
} elseif ($value instanceof DateTimeInterface) {
echo "$type: a date object\n";
} elseif ($value === null) {
echo "$type: no value\n";
}
}
Output
int: integer
string: numeric (int-like or float-like)
float: numeric (int-like or float-like)
null: no value
array: array with 1 element(s)
DateTime: a date object
| Function / Operator | true For |
|---|---|
is_int(), is_float(), is_string(), is_bool(), is_array() | That exact type |
is_numeric() | Numbers and numeric strings ('42', '4.2', '1e3') |
is_null() | null (same as === null) |
instanceof | Objects of a class, subclass, or interface |
Note that is_numeric() accepts strings, so '42' passes it but fails is_int(). With type declarations on function parameters, many of these checks become unnecessary, because PHP enforces the types (see Type Declarations in Functions).
Checking Membership in a List
in_array() With Strict Mode
<?php
$allowedPlans = ['free', 'pro', 'business'];
$plan = 'Pro';
if (in_array(strtolower($plan), $allowedPlans, true)) {
echo "Valid plan\n";
}
Output
Valid plan
Always pass true as the third argument. Without it, in_array() compares loosely (==), which can match unexpected values.
Many Equality Checks → in_array() or match
Instead of:
if ($day === 'Sat' || $day === 'Sun') { ... }
write:
if (in_array($day, ['Sat', 'Sun'], true)) { ... }
The list is easier to extend and read. When each value should produce a different result, use match (see PHP Match).
Keys vs Values
<?php
$prices = ['pen' => 15, 'book' => null];
var_dump(isset($prices['book']));
var_dump(array_key_exists('book', $prices));
var_dump(in_array(15, $prices, true));
Output
bool(false)
bool(true)
bool(true)
isset()isfalsebecause the value isnull.array_key_exists()istruebecause the key exists.in_array()searches the values, not the keys.
Combining and Negating Conditions
Group With Parentheses
<?php
$isAdmin = false;
$isAuthor = true;
$isPublished = false;
if ($isAdmin || ($isAuthor && !$isPublished)) {
echo "You can edit this draft\n";
}
Output
You can edit this draft
&& binds more tightly than ||, so the parentheses do not change the result here — but they make the intended grouping obvious.
Negate the Whole Condition
<?php
$age = 16;
$hasConsent = false;
if (!($age >= 18 || $hasConsent)) {
echo "Registration not allowed\n";
}
if ($age < 18 && !$hasConsent) {
echo "Registration not allowed (simplified)\n";
}
Output
Registration not allowed
Registration not allowed (simplified)
The second form applies De Morgan's law: "not (A or B)" equals "not A and not B". Positive, simplified conditions are usually easier to read than negated groups.
Name Complex Conditions
<?php
$order = ['total' => 1800, 'country' => 'IN', 'weight_kg' => 3];
$isDomestic = $order['country'] === 'IN';
$isLight = $order['weight_kg'] <= 5;
$qualifiesByTotal = $order['total'] >= 999;
if ($isDomestic && $isLight && $qualifiesByTotal) {
echo "Free shipping\n";
}
Output
Free shipping
Each part of the rule has a name, so the if reads like the business rule itself.
Practical Example: Validating a Signup Form
<?php
function validateSignup(array $input): array
{
$errors = [];
$name = trim($input['name'] ?? '');
$email = trim($input['email'] ?? '');
$age = filter_var($input['age'] ?? '', FILTER_VALIDATE_INT);
$plan = $input['plan'] ?? '';
$terms = $input['terms'] ?? null;
if ($name === '') {
$errors['name'] = 'Name is required.';
} elseif (mb_strlen($name) > 50) {
$errors['name'] = 'Name must be at most 50 characters.';
}
if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
$errors['email'] = 'Enter a valid email address.';
}
if ($age === false || $age < 13 || $age > 120) {
$errors['age'] = 'Age must be between 13 and 120.';
}
if (!in_array($plan, ['free', 'pro'], true)) {
$errors['plan'] = 'Choose the free or pro plan.';
}
if ($terms !== 'yes') {
$errors['terms'] = 'You must accept the terms.';
}
return $errors;
}
print_r(validateSignup([
'name' => ' ',
'email' => 'priya@',
'age' => '0',
'plan' => 'gold',
]));
print_r(validateSignup([
'name' => 'Priya',
'email' => 'priya@example.com',
'age' => '24',
'plan' => 'pro',
'terms' => 'yes',
]));
Output
Array
(
[name] => Name is required.
[email] => Enter a valid email address.
[age] => Age must be between 13 and 120.
[plan] => Choose the free or pro plan.
[terms] => You must accept the terms.
)
Array
(
)
How Each Condition Works
| Field | Condition | Why It Is Written This Way |
|---|---|---|
| Name | trim(...) === '', then mb_strlen() > 50 | Whitespace-only names are rejected; length counts characters, not bytes |
filter_var(..., FILTER_VALIDATE_EMAIL) === false | Uses PHP's validator and a strict check against false | |
| Age | $age === false || $age < 13 || $age > 120 | false means "not an integer"; short-circuiting ensures the range checks run only on real integers |
| Plan | !in_array($plan, [...], true) | Allow-list with strict comparison |
| Terms | $terms !== 'yes' | Only the exact expected value counts as acceptance; a missing checkbox gives null |
Separate if statements are used because several fields can be invalid at once, and the user should see all errors together. The elseif inside the name check is used because "required" and "too long" cannot both apply.
Common Mistakes
| Mistake | Effect | Better Condition | ||
|---|---|---|---|---|
if ($x = 5) | Assignment; always true | if ($x === 5) | ||
if (strpos($s, 'a')) | Match at position 0 treated as false | if (str_contains($s, 'a')) | ||
if (empty($qty)) for numeric input | 0 treated as missing | if (trim($qty) === '') then validate | ||
if ($input == 0) | Loose comparison with strings | Validate, then === 0 | ||
if (in_array($v, $list)) | Loose matching | in_array($v, $list, true) | ||
if (!$a > 5) | (!$a) > 5 | if (!($a > 5)) or if ($a <= 5) | ||
| `if ($a | $b && $c)` | Grouping unclear | Add parentheses |
Best Practices
- Use strict comparisons (
===,!==) and strictin_array(). - Trim and validate input before comparing it; compare typed values, not raw strings.
- Choose the right existence check:
isset(),??,array_key_exists(), or an explicit=== ''. - Avoid
empty()for values where0or'0'are valid. - Prefer boolean-returning functions (
str_contains(),filter_var()with strict checks) over position-returning ones in conditions. - Name complex conditions and use parentheses when mixing
&&and||.
Official reference: https://www.php.net/manual/en/language.operators.comparison.php