PHP Operators — Why Loose Equality Exposes User Accounts
A stored hash starting with '0e' plus digits made any password valid in PHP—a classic loose equality bug that strict === avoids.
20+ years shipping production PHP systems at scale. Drawn from code that ran under real load.
- ✓Basic programming fundamentals
- ✓A computer with internet access
- ✓Willingness to follow along with examples
- PHP operators are symbols that perform operations on values: arithmetic, comparison, logical, assignment, and more.
- Every comparison has two versions: loose (==) and strict (===). Mixing them causes most security bugs.
- Assignment operators (+=, -=, .=) combine operation and assignment in one step — use them to signal mutation.
- The null coalescing operator (??) and null-safe operator (?->) eliminate null errors in modern PHP.
- Performance insight: Loose comparisons are slower than strict because PHP must coerce types first.
- Production insight: Using == for password checks can bypass authentication when hash starts with '0e' (PHP type juggling).
PHP operators are the fundamental building blocks that transform static variables into dynamic logic, but they also represent a critical security boundary that many developers underestimate. Operators in PHP are not just syntactic sugar — they directly control how data flows through your application, how comparisons are made, and ultimately how authentication, authorization, and data integrity decisions are executed.
The infamous loose equality operator (==) versus strict equality (===) is a prime example: using == can cause type juggling that treats "0" as equal to 0, or even worse, "0e12345" as equal to "0e67890" because PHP coerces both to zero when they look like scientific notation. This has historically exposed user accounts in authentication systems where password hashes (which often start with 0e followed by digits) could be bypassed by an attacker providing a matching hash string.
Understanding operators as a security boundary means recognizing that every ==, !=, or loose comparison in a conditional is a potential vulnerability that strict operators (===, !==) can eliminate.
PHP operators span several categories that every developer must internalize. Arithmetic operators (+, -, , /, %, *) work as expected but with PHP's dynamic typing — dividing integers can produce floats, and modulus on negatives follows the dividend's sign.
Assignment operators (=, +=, -=, etc.) combine operation and assignment in one step, but the simple = is easily confused with == in conditionals, a classic source of bugs. Comparison operators (==, ===, !=, !==, <, >, <=, >=, <=>) are where the security landmines live: the spaceship operator (<=>) returns -1, 0, or 1 and is safe, but loose comparisons with == can produce unexpected results like "php" == 0 being true because PHP converts the string to an integer (0 if it doesn't start with a number).
Logical operators (&&, ||, !, and, or, xor) short-circuit, meaning false && expensiveFunction() never calls the function — a performance optimization that can also mask bugs if you rely on side effects.
String operators are deceptively simple: . concatenates, .= appends. But concatenation in loops without using an array and is a performance anti-pattern that creates O(n²) memory allocations. Increment/decrement operators (implode()++, --) work on strings in a Perl-like way — ++ on "a" gives "b", on "z" gives "aa" — but -- does not, which catches many developers off guard.
The ternary operator (?:) and null coalescing operator (??) are essential for concise defaults, but the ternary's left-associativity in PHP (unlike most languages) means nested ternaries often evaluate in unexpected order; always parenthesize them. The null-safe operator (?->) introduced in PHP 8.0 short-circuits on null: $user?->profile?->email returns null if $user or $user->profile is null, avoiding fatal errors on chained method/property access.
In production systems handling millions of requests, choosing ?? over ?: or === over == isn't pedantry — it's the difference between a secure login flow and a CVE in your authentication logic.
Think of PHP operators as the verbs of your code — they're the action words that tell PHP what to DO with your data. Just like a calculator has buttons for +, -, × and ÷, PHP has symbols that add numbers, compare values, combine conditions, and assign data to variables. Without operators, you'd have data sitting around doing absolutely nothing — operators are what make things happen.
| Chrome | Firefox | Safari | Edge |
|---|---|---|---|
| ✓ | ✓ | ✓ | ✓ |
Every useful program ever written does at least one of three things: it calculates something, it compares something, or it makes a decision. None of those three things are possible without operators. Whether you're building a shopping cart that totals an order, a login form that checks if a password matches, or a news feed that filters posts by date — operators are working silently behind every single line of that logic. They are the engine of your code.
Before operators existed in programming languages, developers had to write verbose machine-level instructions just to add two numbers. Operators solve this by giving us a clean, readable shorthand. Instead of writing a function call like add($price, $tax), you just write $price + $tax. Instead of a function like isGreaterThan($age, 18), you write $age > 18. Operators compress complex intentions into a single character or short symbol — and PHP has a rich set of them covering arithmetic, comparison, logic, assignment, string manipulation and more.
By the end of this article you'll understand every major category of PHP operator, know exactly when to reach for each one, be able to read and write real PHP logic without second-guessing yourself, and you'll know the two or three sneaky mistakes that trip up even experienced developers. Let's build this up from the ground floor.
Why PHP Operators Are a Security Boundary, Not Just Syntax
PHP operators are symbols that perform operations on values and variables. The core mechanic is that they define how values are compared, combined, or transformed — but the critical distinction is between loose (==) and strict (===) equality. Loose equality performs type juggling before comparison, meaning '1' == 1 is true, while strict equality checks both value and type. This is not a minor detail; it's a fundamental behavior that determines how your application handles authentication, authorization, and data validation. Loose equality coerces strings to numbers, objects to arrays, and nulls to empty strings, creating implicit conversion paths that often bypass intended logic. In practice, this means a user input of '0e12345' compared with == to a password hash starting with '0e' can evaluate as true because PHP interprets both as scientific notation numbers equal to zero. This is not theoretical — it has been exploited in real-world authentication bypasses. Use strict equality (===) for all security-sensitive comparisons, including password verification, session tokens, and user IDs. Loose equality is only safe when you explicitly intend type coercion, such as checking if a form field is empty with == ''.
Arithmetic Operators — PHP as Your Calculator
Arithmetic operators are the ones you already know from primary school maths — they perform mathematical calculations on numbers. PHP gives you addition (+), subtraction (-), multiplication (), division (/), modulus (%), and exponentiation (*).
The one you might not recognise is modulus (%). It gives you the remainder after a division. So 10 % 3 is 1, because 3 goes into 10 three times (that's 9), leaving 1 left over. This is incredibly useful for things like checking whether a number is even or odd — an even number always has a remainder of 0 when divided by 2.
Exponentiation () is also worth highlighting. 2 8 means '2 to the power of 8', which is 256. You'll see this in security calculations, image sizing logic, and anywhere exponential growth matters.
PHP follows standard mathematical order of operations — multiplication and division before addition and subtraction. Use parentheses to override that order and make your intention crystal clear to anyone reading your code later.
<?php // Imagine you're building a simple invoice calculator $itemPrice = 49.99; // price of one item in dollars $quantity = 3; // how many the customer ordered $discountPct = 10; // 10% discount $taxRate = 0.08; // 8% sales tax as a decimal // --- Addition --- $shippingFee = 5.99; $subtotalWithShipping = $itemPrice + $shippingFee; // adds two values echo "Item + Shipping: $" . $subtotalWithShipping . "\n"; // 55.98 // --- Subtraction --- $refundAmount = 10.00; $amountAfterRefund = $itemPrice - $refundAmount; // removes the refund echo "After Refund: $" . $amountAfterRefund . "\n"; // 39.99 // --- Multiplication --- $lineTotal = $itemPrice * $quantity; // price times quantity = line total echo "Line Total: $" . $lineTotal . "\n"; // 149.97 // --- Division --- $discountAmount = ($discountPct / 100) * $lineTotal; // calculate discount value echo "Discount Amount: $" . $discountAmount . "\n"; // 14.997 // --- Modulus (remainder after division) --- $totalItems = 10; $itemsPerBox = 3; $leftoverItems = $totalItems % $itemsPerBox; // 10 divided by 3 = 3 remainder 1 echo "Items that won't fit in a full box: " . $leftoverItems . "\n"; // 1 // A classic use: check if a number is even or odd $orderNumber = 4582; if ($orderNumber % 2 === 0) { echo "Order #" . $orderNumber . " is an even-numbered order.\n"; } else { echo "Order #" . $orderNumber . " is an odd-numbered order.\n"; } // --- Exponentiation --- $base = 2; $power = 10; $result = $base ** $power; // 2 to the power of 10 echo "2 to the power of 10 is: " . $result . "\n"; // 1024 // --- Order of operations matters! --- $wrongTotal = $itemPrice * $quantity + $discountPct / 100; // PHP does * and / first $correctTotal = ($itemPrice * $quantity) + ($discountPct / 100); // parentheses = intention is clear echo "Wrong-looking (but technically same here): $" . $wrongTotal . "\n"; echo "With parentheses (explicit intention): $" . $correctTotal . "\n";
10 / 3 returns 3.3333... (a float), not 3. If you need a whole number result from division, use intdiv(10, 3) which returns 3, or cast with (int)(10 / 3). Never assume PHP integer division truncates like it does in some other languages.bcdiv() for precise decimal arithmetic in payment systems.Assignment Operators — Storing and Updating Values in One Move
The basic assignment operator is =. But here's something beginners always get confused about: in PHP (and most programming languages), = does NOT mean 'equals'. It means 'take the value on the right and store it in the variable on the left'. Think of it like putting a label on a jar.
$score = 100 means: create a jar called score, and put the number 100 inside it.
PHP also gives you compound assignment operators that combine a maths operation with an assignment in one step. Instead of writing $score = $score + 10, you can write $score += 10. These aren't just shortcuts — they make your code easier to read because they communicate 'I am changing this existing value' rather than 'I am creating a new value'.
These compound operators exist for all arithmetic operations: +=, -=, =, /=, %=, *=. There's also .= for strings, which appends text to an existing string variable — something you'll use constantly when building HTML output or log messages.
Using these operators correctly is a sign of confident, fluent PHP code.
<?php // --- Basic Assignment --- $playerName = "Alex"; // store the string "Alex" in $playerName $playerScore = 0; // player starts with zero points $playerLevel = 1; // player starts at level 1 echo "Starting state — Player: $playerName, Score: $playerScore, Level: $playerLevel\n"; // --- += (add and assign) --- // Player picks up a coin worth 50 points $playerScore += 50; // same as: $playerScore = $playerScore + 50 echo "After coin pickup — Score: $playerScore\n"; // 50 // Player gets a time bonus of 30 points $playerScore += 30; echo "After time bonus — Score: $playerScore\n"; // 80 // --- -= (subtract and assign) --- // Player takes damage and loses 15 points $playerScore -= 15; // same as: $playerScore = $playerScore - 15 echo "After taking damage — Score: $playerScore\n"; // 65 // --- *= (multiply and assign) --- // Player activates a double-score power-up! $playerScore *= 2; // same as: $playerScore = $playerScore * 2 echo "After double-score power-up — Score: $playerScore\n"; // 130 // --- /= (divide and assign) --- // Score gets halved for using a hint $playerScore /= 2; // same as: $playerScore = $playerScore / 2 echo "After hint penalty — Score: $playerScore\n"; // 65 // --- %= (modulus and assign) --- // Rarely used, but here's a real example: // "Wrap" the level number to stay within 1-5 cycle $currentLevel = 7; $currentLevel %= 5; // 7 % 5 = 2, wraps back into range 0-4 echo "Wrapped level (0-indexed): $currentLevel\n"; // 2 // --- .= (concatenate and assign) — extremely useful for building strings --- $gameLog = "[LOG] Game started. "; $gameLog .= "Player $playerName joined. "; // appends to existing string $gameLog .= "Final score: $playerScore."; // appends again echo $gameLog . "\n";
.= and echo once at the end: $html = ''; foreach($items as $item) { $html .= "<li>$item</li>"; } echo $html;. This is cleaner, testable, and easier to extend..= and using = instead inside a loop overwrites the variable each iteration..=..= is essential for string building — use it everywhere.Comparison and Logical Operators — The Decision-Making Duo
Comparison operators let PHP evaluate whether something is true or false by comparing two values. Every if statement, every loop condition, every filter you write depends on them. They always return a boolean — either true or false.
The most important distinction in PHP comparisons is == versus ===. Double-equals (==) checks if two values are loosely equal — PHP will try to convert types before comparing. Triple-equals (===) checks if two values are strictly equal — same value AND same type. This difference causes some of the nastiest bugs in PHP.
Logical operators combine multiple comparisons into one decision. && (AND) means both conditions must be true. || (OR) means at least one must be true. ! (NOT) flips a boolean — true becomes false and vice versa.
Think of && and || like real-world logic: 'You can enter the club if you have ID AND you're over 18' is &&. 'You get a discount if you're a student OR a senior' is ||.
There's also the spaceship operator <=>, which returns -1, 0, or 1 — perfect for sorting. And the null coalescing operator ??, which is one of PHP's most practical modern features.
<?php // =========================== // COMPARISON OPERATORS // =========================== $userAge = 20; $minimumAge = 18; $membershipTier = "gold"; // == (loose equality — compares VALUE only, converts types) $numericString = "20"; // this is a STRING, not an integer var_dump($userAge == $numericString); // true — PHP converts "20" to 20 before comparing // === (strict equality — compares VALUE and TYPE) var_dump($userAge === $numericString); // false — 20 (int) is not identical to "20" (string) // != (not equal, loose) var_dump($userAge != 18); // true — 20 is not equal to 18 // !== (not identical, strict) var_dump($userAge !== $numericString); // true — different types // > < >= <= var_dump($userAge > $minimumAge); // true — 20 is greater than 18 var_dump($userAge >= $minimumAge); // true — 20 is greater than or equal to 18 var_dump($userAge < 25); // true — 20 is less than 25 var_dump($userAge <= 20); // true — 20 is less than or equal to 20 echo "\n"; // Spaceship operator <=> (returns -1, 0, or 1) // Great for custom sort functions $priceA = 29.99; $priceB = 49.99; $comparison = $priceA <=> $priceB; // -1 means left is LESS THAN right echo "Spaceship result: " . $comparison . "\n"; // -1 // =========================== // LOGICAL OPERATORS // =========================== $isLoggedIn = true; $hasVerifiedEmail = true; $isAdminUser = false; $isBannedUser = false; // && (AND) — both must be true if ($isLoggedIn && $hasVerifiedEmail) { echo "Access granted: user is logged in AND has verified email.\n"; } // || (OR) — at least one must be true if ($isAdminUser || $hasVerifiedEmail) { echo "Can post content: user is admin OR has verified email.\n"; } // ! (NOT) — flips the boolean if (!$isBannedUser) { echo "User is not banned — showing dashboard.\n"; } // Combining conditions $canPublishPost = $isLoggedIn && $hasVerifiedEmail && !$isBannedUser; echo "Can publish post: " . ($canPublishPost ? 'Yes' : 'No') . "\n"; // Yes // Null coalescing operator ?? (PHP 7+) // Returns the LEFT side if it's set and not null, otherwise returns the RIGHT side $username = null; $displayName = $username ?? "Guest"; // $username is null, so use "Guest" echo "Welcome, " . $displayName . "!\n"; // Welcome, Guest! $loggedInUsername = "sarah_dev"; $displayName2 = $loggedInUsername ?? "Guest"; // $loggedInUsername is set, so use it echo "Welcome, " . $displayName2 . "!\n"; // Welcome, sarah_dev!
0 == 'hello' evaluates to TRUE in PHP versions before 8.0, because PHP converted the string 'hello' to the integer 0. This caused serious security bugs in login systems where an empty password hash compared loosely to 0. Always use === for comparisons unless you have a specific reason not to. Make strict comparison your default habit from day one.==) can cause authentication bypass when string hashes start with '0e'.0 == 0 is true.===) for passwords, hashes, and IDs.===) checks value and type.==) coerces types — dangerous.?? for default values and <=> for custom sorting.String Operators and Concatenation
The dot operator (.) is PHP's string concatenation operator. It joins two strings into one. 'Hello' . ' World' produces 'Hello World'. You'll use it constantly when building messages, HTML, or any textual output.
There's also the compound assignment version (.=) which appends a string to the end of an existing variable. $str .= ' more text' is equivalent to $str = $str . ' more text'. This is your go-to tool for building strings in loops, constructing SQL queries, or accumulating log messages.
One subtle behavior: PHP automatically converts numbers to strings when using the dot operator. So 42 . ' apples' becomes '42 apples'. But be aware that concatenation has lower precedence than arithmetic, so 'Result: ' . 5 + 3 gives 'Result: 8'? Actually it gives '8' because the addition happens first — always wrap arithmetic in parentheses when concatenating.
<?php // --- Basic Concatenation --- $firstName = "John"; $lastName = "Doe"; $fullName = $firstName . " " . $lastName; echo "Full name: " . $fullName . "\n"; // John Doe // Building a sentence $noun = "fox"; $verb = "jumps"; $sentence = "The quick brown " . $noun . " " . $verb . " over the lazy dog."; echo $sentence . "\n"; // Number to string conversion $count = 5; echo "You have " . $count . " new messages.\n"; // You have 5 new messages. // ----- Compound assignment .= ----- $logMessage = "[START] "; $logMessage .= "User logged in. "; $logMessage .= "IP 192.168.1.1. "; $logMessage .= "[END]"; echo $logMessage . "\n"; // [START] User logged in. IP 192.168.1.1. [END] // ----- Precedence Gotcha ----- $price = 10; $tax = 2; // Intent: "Total: 12" $result1 = "Total: " . $price + $tax; // Warning: + has higher precedence than . echo "Without parentheses: " . $result1 . "\n"; // 12 (but produces a warning in PHP 8) $result2 = "Total: " . ($price + $tax); // Correct echo "With parentheses: " . $result2 . "\n"; // Total: 12
.) has LOWER precedence than addition (+). So "Total: " . $price + $tax is evaluated as ("Total: " . $price) + $tax, which produces an unexpected result (and a warning in PHP 8). Always wrap arithmetic in parentheses when concatenating: "Total: " . ($price + $tax)..= inside a loop is efficient, but forgetting to initialise the variable with an empty string causes undefined variable warnings..= with a non-string..=.Increment, Decrement, Ternary, and Null-Safe Operators
PHP has a few more operator types that you'll use every day, and skipping them would leave real gaps in your understanding.
Increment and decrement operators (++ and --) increase or decrease a number by exactly 1. They come in two flavours: pre (++$count) changes the value first, then returns it. Post ($count++) returns the current value first, then changes it. This subtlety matters inside expressions.
The ternary operator (?:) is a compact if/else on one line. $result = condition ? 'if true' : 'if false'. Use it for simple assignments — not for complex logic where a full if/else is clearer.
The null-safe operator (?->, PHP 8.0+) lets you safely call methods on an object that might be null, without throwing an error. Before it existed, you'd need an if ($obj !== null) check every single time. Now one ?-> handles it gracefully.
<?php // =========================== // INCREMENT / DECREMENT // =========================== $pageViews = 100; // Post-increment: RETURNS current value, THEN increments $viewsBeforeIncrement = $pageViews++; // $viewsBeforeIncrement gets 100, THEN $pageViews becomes 101 echo "Returned value (post-increment): " . $viewsBeforeIncrement . "\n"; // 100 echo "pageViews after post-increment: " . $pageViews . "\n"; // 101 // Pre-increment: INCREMENTS first, THEN returns new value $newPageViews = ++$pageViews; // $pageViews becomes 102, THEN $newPageViews gets 102 echo "Returned value (pre-increment): " . $newPageViews . "\n"; // 102 echo "pageViews after pre-increment: " . $pageViews . "\n"; // 102 // Decrement works the same way $stockCount = 5; $stockCount--; // post-decrement — one item sold echo "Stock remaining: " . $stockCount . "\n"; // 4 echo "\n"; // =========================== // TERNARY OPERATOR (?:) // =========================== $cartItemCount = 3; // Long way: if ($cartItemCount > 0) { $cartStatus = "Items in cart"; } else { $cartStatus = "Cart is empty"; } // Ternary — same logic, one line: $cartStatus = ($cartItemCount > 0) ? "Items in cart" : "Cart is empty"; echo "Cart status: " . $cartStatus . "\n"; // Items in cart // Real use — displaying user role label $isAdmin = false; $roleLabel = $isAdmin ? "Administrator" : "Standard User"; echo "Role: " . $roleLabel . "\n"; // Standard User echo "\n"; // =========================== // NULL-SAFE OPERATOR (?->) — PHP 8.0+ // =========================== class UserProfile { public string $bio; public function __construct(string $bio) { $this->bio = $bio; } public function getBio(): string { return $this->bio; } } // Simulate a user that exists and one that doesn't $activeUser = new UserProfile("Full-stack developer from Toronto."); $deletedUser = null; // this user no longer exists // Without null-safe operator, you'd need: // if ($deletedUser !== null) { $bio = $deletedUser->getBio(); } else { $bio = null; } // With null-safe operator (?->) — clean and safe: $activeBio = $activeUser?->getBio(); // works fine, returns the bio $deletedBio = $deletedUser?->getBio(); // $deletedUser is null — returns null safely, no error echo "Active user bio: " . ($activeBio ?? 'No bio available') . "\n"; echo "Deleted user bio: " . ($deletedBio ?? 'No bio available') . "\n";
?? instead of a ternary. $name = $input ?? 'default' is cleaner and more intentional than $name = ($input !== null) ? $input : 'default'. PHP 7.4+ even gives you ??= — the null coalescing assignment: $name ??= 'default' only assigns if $name is currently null.if ($i++ < $limit) uses the old value, so it may run one extra iteration.++$i) in loop conditions, or increment after the condition.?->) prevents errors from null objects.Bitwise and Type Operators — Low-Level Power and Type Safety
PHP also provides bitwise operators (&, |, ^, ~, <<, >>) that work on the binary representation of integers. These are often used for flags, permissions, or low‑level data manipulation. For example, you can pack multiple boolean flags into a single integer using bitwise OR and check them with AND.
Type operators help you inspect and enforce variable types. instanceof checks whether an object is an instance of a specific class (or implements an interface). The @ error control operator suppresses errors from an expression — but use it sparingly, as it often hides real problems.
Bitwise operators have lower precedence than comparison operators, so always parenthesize your expressions when mixing them.
<?php // =========================== // BITWISE OPERATORS // =========================== // Define permission flags as powers of 2 $PERMISSION_READ = 1; // 001 $PERMISSION_WRITE = 2; // 010 $PERMISSION_EXECUTE = 4; // 100 // Assign user permissions using bitwise OR $userPermissions = $PERMISSION_READ | $PERMISSION_EXECUTE; // 101 = 5 // Check permissions using bitwise AND if ($userPermissions & $PERMISSION_READ) { echo "User has READ permission.\n"; } if ($userPermissions & $PERMISSION_WRITE) { echo "User has WRITE permission.\n"; } else { echo "User does NOT have WRITE permission.\n"; } // Bitwise shift $value = 4; // 100 in binary $shiftedLeft = $value << 1; // 1000 = 8 $shiftedRight = $value >> 1; // 10 = 2 echo "4 << 1 = " . $shiftedLeft . "\n"; echo "4 >> 1 = " . $shiftedRight . "\n"; // =========================== // TYPE OPERATORS // =========================== class User {}; class Admin extends User {}; $user = new Admin(); echo "Is user an instance of User? " . ($user instanceof User ? "Yes" : "No") . "\n"; // Yes echo "Is user an instance of Admin? " . ($user instanceof Admin ? "Yes" : "No") . "\n"; // Yes // Error control operator @ — suppress warnings (use sparingly) $result = @file_get_contents("nonexistent.txt"); // Suppresses warning, $result = false if ($result === false) { echo "File not found (error suppressed).\n"; }
@ error suppression hides critical failures like database connection errors.instanceof ensures type safety for operations.@ — it hides bugs that will bite you later.Array Operators — Merging Without the Side Effects
Array operators don't get enough respect until you're debugging why a merge silently dropped a key. The + operator is not array_merge. It's a union that keeps the left operand's keys if they already exist. That means $a + $b is the same as writing: take everything from $a, then append anything from $b that isn't already set. No overwrites. No surprises. Use this when you want default values that never get overridden — like config arrays. == and === compare arrays structurally. === checks order and types too. That catches you when two arrays have the same values but different internal pointers. If you're ever tempted to just to compare arrays, stop. Use serialize()===. It's faster and doesn't serialize the whole object graph. The null coalescing operator ?? also works on arrays: $config['timeout'] ?? 30 won't throw an undefined index notice. Use that instead of chains.isset()
// io.thecodeforge — php tutorial $defaults = [ 'host' => 'localhost', 'port' => 5432, 'timeout' => 30, ]; $user_config = [ 'host' => 'prod-db.internal', 'port' => 5432, 'dbname' => 'orders', ]; // Union keeps first-match keys — user config wins $merged = $user_config + $defaults; var_dump($merged); /* array(4) { ["host"]=> string(19) "prod-db.internal" ["port"]=> int(5432) ["dbname"]=> string(6) "orders" ["timeout"]=> int(30) } */
array_merge() when you meant the union operator. array_merge reindexes numeric keys and overwrites string keys from the right. That's two different behaviours — pick the right tool.+ for defaults you keep, array_merge() for overwrites you control.Execution Operators — Backticks Are a Production Footgun
Backticks in PHP run shell commands. $output = \ls -la\; returns stdout as a string. That's all it does. No error handling. No stderr. No timeout. No control. In production, that's a footgun. One runaway gzip process or a missing binary and your script hangs indefinitely — or worse, exposes internal data if you accidentally interpolate user input. The function does the same thing but is more explicit. At least it's easier to grep for in a code review. Never, ever pass unsanitised input into a backtick expression. That's a command injection vulnerability waiting to happen. If you must execute a system command, use shell_exec() or proc_open()symfony/process. Those give you timeout, stderr capture, and proper exit code handling. Backticks are fine for one-liner dev scripts on your local machine. That's it.
// io.thecodeforge — php tutorial $target = $_GET['host'] ?? ''; // NEVER DO THIS — direct user input into backticks $result = `ping -c 2 $target`; echo $result; // Safer but still not great — no timeout, no stderr $escaped = escapeshellarg($target); $result = `ping -c 2 $escaped`; echo $result; // The right way: use symfony/process or proc_open // proc_open gives you pipes for stdin/stdout/stderr + exit codes
escapeshellarg(), you still can't set a timeout. A slow command blocks every subsequent request on that process.proc_open() or symfony/process instead.Loose Equality Bug Exposed User Accounts
password_verify() which uses strict comparison internally.- Never use loose comparison (==) for security-sensitive checks.
- Always use strict comparison (===) by default.
- Use PHP's built-in password functions which handle comparison safely.
if ($var = 'admin') vs if ($var === 'admin').var_dump() on the operands to see actual types..= (append) not = (assignment) inside the loop. var_dump the string each iteration.var_dump($a, gettype($a)); var_dump($b, gettype($b));Compare using === after ensuring types match: if ($a === $b) { ... }is_int(), is_string().grep -n 'if (\$[a-zA-Z_][a-zA-Z0-9_]* = ' yourfile.phpAdd parentheses to highlight: if (($var = 'admin')) will warn in PHP 8+.php -v | grep -E '^PHP'Replace with null check: if ($obj !== null) { $obj->method(); } else { ... }| Operator | Symbol | What It Does | Returns | When To Use It |
|---|---|---|---|---|
| Loose Equality | == | Compares values after type coercion | bool | Almost never — prefer === |
| Strict Equality | === | Compares value AND type — no coercion | bool | Default choice for all equality checks |
| Loose Inequality | != | True if values differ after coercion | bool | Almost never — prefer !== |
| Strict Inequality | !== | True if value OR type differs | bool | Default choice for inequality checks |
| Ternary | ?: | Compact if/else for simple assignments | mixed | Short, readable one-liners only |
| Null Coalescing | ?? | Returns left side if set and non-null, else right | mixed | Default values, especially from $_GET/$_POST |
| Spaceship | <=> | Returns -1, 0, or 1 for ordering | int (-1/0/1) | Custom usort() comparison callbacks |
| Null-Safe | ?-> | Calls method/property only if object is not null | mixed or null | Optional relationships — e.g. $user?->getProfile() |
| Concatenation | . | Joins two strings into one | string | Building strings, HTML output, messages |
| Logical AND | && | True only if BOTH sides are true | bool | Multi-condition if statements |
| Logical OR | || | True if AT LEAST ONE side is true | bool | Fallback conditions, permission checks |
| Instanceof | instanceof | Checks if object is of a specific class/interface | bool | Type checks, especially in dependency injection |
| Bitwise AND | & | Performs bitwise AND on two integers | int | Flag checking, low-level data manipulation |
| Bitwise OR | | | Performs bitwise OR on two integers | int | Building permission flags from enumerated values |
| Error Control | @ | Suppresses errors from an expression | mixed | Rarely; only when you handle failure explicitly |
| File | Command / Code | Purpose |
|---|---|---|
| arithmetic_operators.php | $itemPrice = 49.99; // price of one item in dollars | Arithmetic Operators |
| assignment_operators.php | $playerName = "Alex"; // store the string "Alex" in $playerName | Assignment Operators |
| comparison_logical_operators.php | $userAge = 20; | Comparison and Logical Operators |
| string_operators.php | $firstName = "John"; | String Operators and Concatenation |
| increment_ternary_null_safe.php | $pageViews = 100; | Increment, Decrement, Ternary, and Null-Safe Operators |
| bitwise_type_operators.php | $PERMISSION_READ = 1; // 001 | Bitwise and Type Operators |
| ConfigFallbackMerge.php | $defaults = [ | Array Operators |
| PingCheckDangerous.php | $target = $_GET['host'] ?? ''; | Execution Operators |
Key takeaways
bccomp() or a tolerance for financial calculations.Common mistakes to avoid
5 patternsUsing == instead of === for equality checks
0 == 'admin' returns true in PHP 7 (because 'admin' converts to 0), which can allow empty-password logins to bypass authentication checks.Confusing = (assignment) with == (comparison) inside an if statement
if ($userRole = 'admin') instead of if ($userRole == 'admin'). The first one ASSIGNS 'admin' to $userRole and always evaluates to true — your if block runs for every user regardless of their actual role.if ('admin' === $userRole) so a typo like if ('admin' = $userRole) causes an obvious parse error.Misunderstanding post-increment vs pre-increment in expressions
$count = 5; $result = $count++ 2; — beginners expect $result to be 12 (6 × 2), but it's actually 10 (5 × 2), because $count is used before* being incremented.++$count) or put the increment on its own line before the expression to make the order unambiguous.Using AND/OR instead of &&/|| due to precedence differences
$result = true AND false; assigns true to $result and then evaluates false separately, because AND has lower precedence than =. This leads to logic bugs that are hard to spot.&& and || for logical operations. Reserve the words and, or, xor only when you need the extremely low precedence (rarely). When in doubt, use parentheses.Assuming floating point comparisons with == are safe
0.1 + 0.2 == 0.3 evaluates to false due to IEEE 754 rounding errors. This breaks financial calculations, payment validation, and score comparisons.bccomp() for decimal comparisons, or compare with a tolerance: abs($a - $b) < 0.0001. Never use == on floats.Interview Questions on This Topic
What is the difference between == and === in PHP, and can you give an example where using == would produce a surprising result?
0 == 'hello' returns true in PHP 7 because 'hello' is converted to 0. Always use === unless you explicitly need coercion.What does the spaceship operator <=> return, and in what real-world scenario would you use it?
usort($items, fn($a,$b) => $a['price'] <=> $b['price']).If $a = 0 and $b = 'hello', what does var_dump($a == $b) output in PHP 7 versus PHP 8, and why did the behaviour change?
0 == 'hello' outputs bool(true) because 'hello' is converted to integer 0. In PHP 8, this comparison outputs bool(false) because PHP 8 improved type juggling for string-to-number comparisons — non-numeric strings are no longer converted to 0. This change fixes a class of security bugs.Explain the difference between post-increment ($x++) and pre-increment (++$x), and give an example where mixing them up causes a bug.
while ($i++ < 10) { echo $i; } outputs 1 to 10 (if $i starts at 0) because the condition uses the old value. If you intended to loop with $i from 0 to 9, use ++$i or a for loop. Another example: $result = $count++ * 2 gives wrong result if you expected the incremented value.What is the null coalescing operator (??) and how does it differ from the ternary operator with isset()?
$x ?? $default returns $x if it exists and is not null, otherwise $default. It's equivalent to isset($x) ? $x : $default. Unlike the ternary operator, it does not raise a notice if the variable is undefined. The null coalescing assignment ??= assigns only if the variable is currently null.Frequently Asked Questions
== is the loose equality operator — it compares values but first converts both sides to a common type. === is the strict equality operator — it requires both the value AND the type to match exactly. For example, 0 == false is true, but 0 === false is false because one is an integer and one is a boolean. Always prefer === to avoid unexpected type-coercion bugs.
The modulus operator returns the remainder after dividing two numbers. So 10 % 3 is 1, because 3 goes into 10 three times (making 9), with 1 left over. Its most common use is checking if a number is even ($n % 2 === 0) or cycling through a fixed-size range — for example, keeping a counter between 0 and 4 using $counter % 5.
Use the ternary operator ?: for simple, short assignments where both the true and false outcomes are concise values — for example, $label = $isAdmin ? 'Admin' : 'User'. If the condition is complex, if you're calling functions in both branches, or if either outcome spans more than a few characters, use a full if/else block. Readability always wins — a ternary that requires a second read is the wrong choice.
The null-safe operator ?-> is used to call a method or access a property on an object that might be null. If the object is null, the expression returns null without throwing an error. The null coalescing operator ?? returns the left side if it exists and is not null, otherwise returns the right side. They are often chained: $user?->getProfile()?->bio ?? 'No bio'.
Because floating point arithmetic is not exact in binary. 0.1 and 0.2 cannot be represented exactly in IEEE 754, and the sum is slightly off from 0.3. Never use == on floats; instead compare with a small tolerance, e.g., abs(0.1 + 0.2 - 0.3) < 0.00001. For precise decimal arithmetic, use the bcmath extension.
20+ years shipping production PHP systems at scale. Drawn from code that ran under real load.
That's PHP Basics. Mark it forged?
6 min read · try the examples if you haven't