Why does == behave strangely in PHP?

Short answer

== converts operands to a common type before comparing, which produces results that look wrong. === compares type and value, and should be your default.

php
0 == "a"          // false in PHP 8 (was true in PHP 7)
"1" == "01"       // true  — both converted to int 1
"10" == "1e1"     // true  — both are numeric strings
100 == "1e2"      // true
null == false     // true
[] == false       // true
"abc" == 0        // false in PHP 8, true in PHP 7

PHP 8 fixed the worst of these — comparing a number to a non-numeric string now converts the number to a string rather than the reverse. But numeric strings still compare loosely, and that is enough to cause real bugs.

#Just use ===

php
"1" === "01"      // false
100 === "1e2"     // false
null === false    // false

=== is true only when the type and the value both match. Same for !==.

#Where it actually bites

php
in_array('1', $ids);            // loose by default
in_array('1', $ids, true);      // strict — do this

array_search($needle, $hay, true);
array_keys($array, $value, true);

These functions default to loose comparison. Always pass the strict flag.

#The switch statement is loose

switch uses == internally and cannot be made strict. When the cases are typed values, use match (PHP 8+), which is strict:

php
$label = match($status) {
    1, 2 => 'active',
    0    => 'inactive',
    default => 'unknown',
};

#And functions that return 0 or false

php
if (strpos($haystack, $needle) !== false) { }

strpos returns 0 for a match at the start, which is falsy. !== false is the only correct check. The same applies to filter_var, which returns false on failure but can legitimately return 0.