
Разберём классику жанра — сравнение строки с числом:
var_dump(0 == "abc"); // bool(false) — в PHP 8+
var_dump(0 == "0"); // bool(true)
var_dump("abc" == "0"); // bool(false)
var_dump("1" == "01"); // bool(true)
var_dump("10" == "1e1"); // bool(true)
var_dump(100 == "1e2"); // bool(true)
Отдельно нужно сказать про историческое поведение — в PHP до версии 8.0 сравнение 0 == "abc" возвращало true, потому что нечисловая строка при сравнении с числом приводилась к нулю. Это и есть тот самый печально известный баг, из-за которого строка-хэш, случайно начинающаяся с «0e» (например, "0e123456"), при сравнении через == с другой похожей строкой считалась равной нулю — а значит, и друг другу, даже будучи совершенно разными значениями. На этом строилась реальная уязвимость — обход проверки пароля или токена, если сравнение велось через == вместо ===. В PHP 8 это поведение исправили: сравнение числа со строкой теперь, наоборот, приводит число к строке, а не строку к числу, если строка не является числовой. Но это не повод расслабляться — множество серверов и легаси-проектов до сих пор крутятся на PHP 7.x, где старое поведение сохраняется.
Вот с чем конкретно спотыкаются чаще всего:
1. Проверка результата функции, которая может вернуть false или 0. Классический пример — strpos():
$str = "Привет, мир!";
if (strpos($str, "Привет") == false) {
echo "Подстрока не найдена";
}
Проблема в том, что strpos() возвращает позицию вхождения подстроки, и если искомая строка находится в самом начале (позиция 0), функция вернёт 0. А 0 == false — это true. В итоге код ошибочно решает, что подстрока не найдена, хотя она буквально в начале строки. Правильная проверка — через ===:
if (strpos($str, "Привет") === false) {
echo "Подстрока не найдена";
}
2. Сравнение id, пришедших из разных источников. Одно из значений пришло из базы данных как число (int), другое — из $_GET или формы как строка:
$userId = 5; // число из базы
$requestId = "5"; // строка из $_GET['id']
var_dump($userId == $requestId); // true — и это ок в большинстве случаев
var_dump($userId === $requestId); // false — а вот тут можно словить баг на ровном месте
Здесь, наоборот, === может стать источником проблемы, если разработчик не подумал о приведении типов заранее — именно поэтому многие явно приводят тип через (int)$requestId перед строгим сравнением, а не полагаются на нестрогое ==.
3. Сравнение с null. Частая ошибка — проверять переменную на null через ==:
$value = 0;
if ($value == null) {
echo "Значения нет"; // выполнится, хотя 0 — это вполне себе значение
}
0, "", "0", false, пустой массив [] — все эти значения при сравнении через == с null дадут true, хотя логически это разные состояния переменной. Для проверки именно на null правильнее использовать is_null($value) или строгое $value === null.
Практическое правило, которое стоит просто взять за привычку: используйте === по умолчанию везде, где сравниваете значения, и переключайтесь на == только тогда, когда осознанно хотите получить автоматическое приведение типов и понимаете, как именно PHP его выполнит в вашем случае. Такой подход резко сокращает число багов, которые проявляются только на специфичных входных данных и не ловятся при поверхностном тестировании.
Отдельно стоит упомянуть функцию in_array(), у которой по умолчанию тоже используется нестрогое сравнение:
$arr = ["admin", "editor", 0];
var_dump(in_array("moderator", $arr)); // true! "moderator" == 0 ⇒ true (до PHP 8)
var_dump(in_array("moderator", $arr, true)); // false — со строгим сравнением всё честно
Третий необязательный аргумент true включает строгий режим и для in_array(), и для родственной ей array_search() — про этот параметр забывают регулярно, а стоит держать его в голове как стандарт, а не как опцию «на всякий случай».
Если резюмировать: == и === — это не взаимозаменяемые операторы с разным уровнем строгости, это два разных способа мышления о сравнении данных. == спрашивает «похожи ли эти значения по смыслу», === — «одинаковы ли они вообще, включая тип». Пока оба значения гарантированно одного типа, разницы на практике нет. Но как только в цепочку попадают данные из внешних источников — форм, базы, API, — риск получить неожиданное true там, где логически должно быть false, резко возрастает, и единственная защита от этого — привычка писать === рефлекторно, а не по необходимости.









