核心規則:PHP 型別轉換比較表
| 運算子 | 名稱 | 比較邏輯 | 範例結果 |
== | 鬆散比較 | **轉換型別後比較值** | 0 == 'abc' → true, '10' == 10 → true |
=== | 嚴格比較 | **型別與值皆相同** | 0 === '0' → false, 10 === '10' → false |
!= / <> | 不等 (鬆散) | 同上轉換 | |
!== | 不全等 | 型別或值不同 | |
經典陷阱案例
1. 0 與非數字字串比較永遠為 true
<?php
var_dump(0 == 'abc'); // true ← 字串轉數字 = 0
var_dump(0 == '0xFF'); // true ← '0xFF' 轉數字 = 0 (非十進位)
var_dump(0 == '1abc'); // false ← '1abc' 轉數字 = 1
var_dump(0 == ''); // true ← 空字串轉數字 = 0
var_dump(0 == null); // true
var_dump(0 == false); // true
原因:PHP 將字串轉換為數字時,從開頭解析數字字元,遇到非數字停止;無數字字元則為 0。
2. 長數字字串 (身分證、手機、訂單號) 使用 == 比較會精度遺失
<?php
$id1 = 'A123456789'; // 身分證字號
$id2 = 'A123456788';
var_dump($id1 == $id2); // true!PHP 將兩者轉為浮點數 1.23456789E+8 相等
// 科學記號精度約 14-15 位有效數字,超過部分捨棄
// 正確:嚴格比較
var_dump($id1 === $id2); // false
// 或使用 strcmp
var_dump(strcmp($id1, $id2) === 0); // false
3. 十六進位/科學記號字串隱性轉換
<?php
var_dump('0xFF' == 255); // true (十六進位)
var_dump('010' == 8); // true (八進位,PHP 8.1+ 棄用)
var_dump('1e3' == 1000); // true (科學記號)
var_dump('1000' == '1e3'); // true (雙方皆轉數字)
// 陷阱:'0xDeadBeef' == '0xdeadbeef' → true (不分大小寫)
4. 陣列/物件比較
<?php
$a = [1,2,3]; $b = [1,2,3];
var_dump($a == $b); // true (鍵值對相同)
var_dump($a === $b); // true (鍵值對相同且順序、型別相同)
$a = ['a'=>1]; $b = [0=>1];
var_dump($a == $b); // true (鬆散忽略鍵名型別)
var_dump($a === $b); // false (嚴格鍵名型別不同)
class A { public $x=1; }
class B { public $x=1; }
$a = new A(); $b = new B();
var_dump($a == $b); // true (屬性相同)
var_dump($a === $b); // false (不同類別/實例)
最佳實踐:永遠使用 === (嚴格比較)
<?php
// 原則:除非明確需要型別轉換,否則一律 ===
function isEqual(mixed $a, mixed $b): bool {
return $a === $b;
}
// 特例:從外部輸入 (GET/POST/JSON) 取得字串,需比較數值
function isNumericEqual(string $input, int|float $expected): bool {
// 先驗證格式,再轉型
if (!preg_match('/^-?\d+(\.\d+)?$/', $input)) return false;
return (float)$input === $expected;
}
// 身分證/手機/訂單號比較
function isIdEqual(string $a, string $b): bool {
return $a === $b; // 或 strcmp($a, $b) === 0
}
PHP 8.0+ 新特性:非嚴格比較行為微調
0 == 'foobar' 仍為 true (不變)。
'' == 0 仍為 true。
- 字串與數字比較現在會發出
E_WARNING (PHP 8.1+):「非數字字串與數字比較」—— **強烈建議開啟錯誤報告捕捉此類隱性 Bug**。
<?php
error_reporting(E_ALL);
ini_set('display_errors', '1');
var_dump(0 == 'abc'); // PHP 8.1+ 產生 Warning: "abc" is not a well-formed number
比較函式選擇指南
| 場景 | 推薦函式/運算子 | 理由 |
| 一般變數相等 | === | 型別安全、效能最好 |
| 字串相等 (大小寫敏感) | === 或 strcmp($a,$b)===0 | 避免數字轉換 |
| 字串相等 (大小寫不敏感) | strcasecmp($a,$b)===0 | 區域設定無關 |
| 浮點數近似相等 | abs($a-$b) < 0.00001 | 避免二進位浮點誤差 |
| 陣列鍵值對相等 | $a === $b | 順序、型別皆同 |
| 陣列內容相等 (忽略順序) | array_diff($a,$b) === [] && array_diff($b,$a) === [] | 或 sort($a); sort($b); $a===$b |
| 物件同一性 | $a === $b | 同一實例 |
| 物件值相等 | 實作 __equals() 或自訂比較 | PHP 無內建值相等 |
實務防護清單
- 程式碼規範:ESLint/PHP_CodeSniffer 規則禁止
==、!=,強制 ===/!==。
- 靜態分析:PHPStan Level 5+ 會偵測鬆散比較並報警。
- 單元測試:針對邊界值 (0, '', '0', '0.0', 'abc', '1e3', '0xFF') 撰寫比較測試。
- 資料庫查詢:WHERE 子句隱性轉型同樣危險,
WHERE phone = 13800000000 若 phone 為 VARCHAR 會全表掃描且可能轉型錯誤,務必 WHERE phone = '13800000000'。
- API 文件:明確標註參數型別 (string/int/float),前後端協定不依賴隱性轉換。