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).
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.
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.
.= 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.
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.
.) 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.
?? 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.
@ 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 serialize() just to compare arrays, stop. Use ===. 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 isset() chains.
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.
escapeshellarg(), you still can't set a timeout. A slow command blocks every subsequent request on that process.proc_open() or symfony/process instead.Nullsafe Operator ?-> (PHP 8.0)
The nullsafe operator ?-> was introduced in PHP 8.0 to simplify chained method calls or property accesses on potentially null values. Instead of writing nested null checks, you can use ?-> to short-circuit the chain if any element is null, returning null immediately. This is particularly useful when working with deeply nested objects from APIs or databases.
Consider a scenario where you have a user object that may have an address, and that address may have a city. Without the nullsafe operator, you'd write:
$city = null;
if ($user !== null) {
$address = $user->getAddress();
if ($address !== null) {
$city = $address->getCity();
}
}
With the nullsafe operator, this becomes:
$city = $user?->getAddress()?->getCity();
If $user is null, the entire expression returns null without throwing an error. Similarly, if getAddress() returns null, the chain stops and returns null.
Important: The nullsafe operator only works for method calls and property accesses, not for array accesses or function calls. Also, it cannot be used on the left side of an assignment.
This operator is a game-changer for readability and reduces boilerplate null-checking code. However, be cautious: overusing it can mask logic errors where a null value is unexpected. Use it when null is a valid and expected state.
?-> allows safe chaining of method calls and property accesses on potentially null values, returning null if any part of the chain is null.Null Coalescing Assignment ??= (PHP 7.4)
The null coalescing assignment operator ??= was introduced in PHP 7.4 as a shorthand for assigning a value only if the variable is null. It combines the null coalescing operator ?? with assignment. The syntax $var ??= $value is equivalent to $var = $var ?? $value. This is extremely useful for setting default values without overwriting existing non-null values.
For example, consider a configuration array where you want to set a default value for a key only if it's not already set:
``php $config['timeout'] ??= 30; ``
This is much cleaner than:
``php if (!isset($config['timeout'])) { $config['timeout'] = 30; } ``
Or using the ternary operator:
``php $config['timeout'] = $config['timeout'] ?? 30; ``
The ??= operator works with any variable, including array elements and object properties. It only assigns if the current value is null (or undefined, which is treated as null). Note that it does not check for empty strings or zero; only null (and undefined) triggers the assignment.
This operator is particularly handy in loops or when processing data where you want to ensure a default value without overwriting existing data. It improves readability and reduces the chance of errors from manual null checks.
??= is ideal for setting defaults in configuration arrays or initializing optional fields without overwriting existing data. It reduces boilerplate and potential bugs from manual isset checks.??= sets a default value only if the variable is null, simplifying code that initializes variables with fallbacks.Match Expression vs Switch (PHP 8.0)
PHP 8.0 introduced the match expression as a more powerful and concise alternative to the traditional switch statement. Unlike switch, match is an expression (it returns a value) and uses strict comparison (===) instead of loose comparison (==). This eliminates common bugs where type juggling causes unexpected matches.
Key Differences: - match returns a value; switch does not. - match uses strict comparison; switch uses loose comparison. - match can combine multiple conditions with a comma; switch requires fall-through or break. - match throws an UnhandledMatchError if no arm matches (unless a default is provided); switch silently continues.
Example:
```php $statusCode = 404;
// Switch (loose comparison) switch ($statusCode) { case 200: case 302: $message = 'OK'; break; case 404: $message = 'Not Found'; break; case 500: $message = 'Server Error'; break; default: $message = 'Unknown'; }
// Match (strict comparison) $message = match ($statusCode) { 200, 302 => 'OK', 404 => 'Not Found', 500 => 'Server Error', default => 'Unknown', }; ```
Notice how match is more concise and eliminates the need for break statements. It also forces you to handle all cases explicitly (or provide a default), reducing bugs from forgotten cases.
When to use each: - Use match when you need to return a value and want strict comparison. - Use switch when you need side effects (like multiple statements per case) or when loose comparison is intentional.
match is generally preferred for new code due to its safety and readability.
match over switch for new code to avoid type juggling bugs and improve readability. However, keep switch for cases where you need fall-through or multiple statements per case.match expression in PHP 8.0 provides strict comparison, returns a value, and is more concise than switch, making it the safer and more readable choice for most conditional logic.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().| 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 |
| nullsafe.php | class Address { | Nullsafe Operator ?-> (PHP 8.0) |
| null_coalescing_assignment.php | $config = [ | Null Coalescing Assignment ??= (PHP 7.4) |
| match_vs_switch.php | $statusCode = '404'; // string | Match Expression vs Switch (PHP 8.0) |
Key takeaways
bccomp() or a tolerance for financial calculations.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.Frequently Asked Questions
20+ years shipping production PHP systems at scale. Drawn from code that ran under real load.
That's PHP Basics. Mark it forged?
9 min read · try the examples if you haven't