Two kinds of equality
PHP has two equality operators, and choosing the wrong one is a classic source of bugs.
==is loose equality. If the two sides have different types, PHP converts them to a common type first, then compares.===is strict equality, also called identity. It is true only when both the type and the value match.
<?php
var_dump(5 == "5");
var_dump(5 === "5");
var_dump(1.0 == 1);
var_dump(1.0 === 1);
var_dump(null == false);
var_dump(null === false);bool(true)
bool(false)
bool(true)
bool(false)
bool(true)
bool(false)The not-equal operators follow the same pattern: != is loose and !== is strict.
Type juggling
The automatic conversion that == performs is called type juggling. It can feel helpful, because form fields always arrive as strings and "5" == 5 just works. But the rules have corners:
<?php
var_dump("1" == "01");
var_dump("10" == "1e1");
var_dump(100 == "1e2");
var_dump("abc" == 0);
var_dump(null == 0);
var_dump([] == false);bool(true)
bool(true)
bool(true)
bool(false)
bool(true)
bool(true)When both strings look like numbers, == compares them as numbers, which is why "1" == "01" and "10" == "1e1" are true. That surprises people comparing codes or IDs: two different discount codes that happen to look like numbers can be considered equal. With ===, strings are compared character by character, so "1" === "01" is false.
What PHP 8 changed
Before PHP 8, comparing a number with a non-numeric string converted the string to a number. "abc" became 0, so 0 == "abc" was true, a long-standing trap that caused security bugs. PHP 8 reversed it: when one side is a number and the other is a non-numeric string, the number is turned into a string and they are compared as strings.
<?php
var_dump(0 == "abc");
var_dump(0 == "");
var_dump("1" == " 1");
var_dump(42 == "42abc");bool(false)
bool(false)
bool(true)
bool(false)Leading whitespace is still accepted in numeric strings (" 1"), but trailing text such as "42abc" makes a string non-numeric. You may still meet older code or tutorials that rely on the PHP 7 behaviour.
Use === by default
A simple rule removes almost all of these puzzles: use === and !== unless you specifically want conversion. Convert input on purpose first, then compare strictly:
<?php
$quantityFromForm = "3"; // form values are always strings
$quantity = (int) $quantityFromForm;
if ($quantity === 3) {
echo "Three items\n";
}
$status = "paid";
if ($status !== "cancelled") {
echo "Order is active\n";
}Three items
Order is activeLoose comparison inside functions
Some built-in functions compare loosely by default. in_array() and array_search() take a third argument that switches them to strict comparison:
<?php
$allowedIds = [10, 20, 30];
var_dump(in_array("1e1", $allowedIds));
var_dump(in_array("1e1", $allowedIds, true));
var_dump(array_search("20", $allowedIds));
var_dump(array_search("20", $allowedIds, true));bool(true)
bool(false)
int(1)
bool(false)Passing true is a good habit, especially when the value comes from a user. switch also compares loosely, while match, covered in switch and match, compares strictly.
Comparing strings and arrays
<, > and <=> work on strings too. They compare byte by byte, so uppercase letters sort before lowercase ones. For human-friendly ordering use strcmp(), strcasecmp() (case-insensitive) or strnatcmp() (natural order, so "item2" comes before "item10"):
<?php
var_dump("apple" < "banana");
var_dump("Zebra" < "apple");
echo strcasecmp("Zebra", "apple") > 0 ? "Zebra after apple\n" : "Zebra first\n";
echo strnatcmp("item2", "item10") < 0 ? "item2 first\n" : "item10 first\n";bool(true)
bool(true)
Zebra after apple
item2 firstArrays are equal with == when they have the same key-value pairs, and identical with === when the order and types also match.
Strict types for functions
Type juggling also happens when you pass a value to a function with a typed parameter. By default PHP converts "5" to 5 for an int parameter. Putting declare(strict_types=1); as the very first statement of a file turns that off for calls made from that file, so a wrong type throws a TypeError instead:
<?php
declare(strict_types=1);
function double(int $number): int
{
return $number * 2;
}
echo double(5) . "\n";
echo double("5") . "\n";10
Fatal error: Uncaught TypeError: double(): Argument #1 ($number) must be of type int, string given, called in /path/to/script.php on line 10 and defined in /path/to/script.php:4
Stack trace:
#0 /path/to/script.php(10): double('5')
#1 {main}
thrown in /path/to/script.php on line 4Many modern projects, including frameworks, use strict types in every file. It catches mistakes early, at the cost of converting input explicitly. Parameter types are covered in Parameters, defaults, named arguments and types.
Common mistakes
- Using == to compare codes, IDs or hashes that may look numeric. Use
===, orhash_equals()for secrets. - Forgetting the strict flag in
in_array()andarray_search(). - Checking a function's result with == false when it can legitimately return 0, such as
strpos(). Use=== false. - Assuming PHP 7 rules when reading old answers online about
0 == "abc".
Exercise
strpos("PHP is fun", "PHP") returns 0, the position where "PHP" starts. Write code that prints "found" if the word is in the string and "not found" otherwise, and explain why if (strpos(...) == false) would give the wrong answer.
Show one possible answer
<?php
$position = strpos("PHP is fun", "PHP");
if ($position === false) {
echo "not found\n";
} else {
echo "found at $position\n";
}
var_dump($position == false);found at 0
bool(true)The text was found at position 0, but 0 == false is true, so a loose comparison would report "not found". Since PHP 8, str_contains() answers the simpler question directly and returns a real boolean.
Key points
- == compares after converting both sides to a common type (loose). === also requires the same type (strict).
- Since PHP 8, comparing a number with a non-numeric string compares them as strings, so 0 == 'abc' is false.
- Functions such as in_array() and array_search() compare loosely unless you pass true as the strict argument.
- declare(strict_types=1); makes PHP reject wrong scalar types passed to typed function parameters instead of converting them.