类型比较表
概述
PHP 的类型比较系统是许多 bug 的来源。PHP 提供两种比较方式:松散比较(==) 和 严格比较(===)。松散比较会在比较前自动进行类型转换,而严格比较要求类型和值都相同。理解比较规则是编写安全 PHP 代码的基础。
核心要点
==松散比较:比较前自动类型转换,只比较值。===严格比较:类型和值都必须相同,不进行类型转换。0、"0"、null、""、false是 PHP 中最常见的比较陷阱。- PHP 8.0 修复了许多不合理的松散比较行为。
- 始终使用
===进行比较,除非你明确需要松散比较。 - :::
基础概念
松散比较 vs 严格比较
| 运算符 | 名称 | 类型转换 | 比较规则 |
|---|---|---|---|
== | 松散比较(等于) | 是 | 转换为相同类型后比较值 |
=== | 严格比较(全等) | 否 | 类型和值都必须相同 |
!= | 松散不等于 | 是 | 与 == 相反 |
!== | 严格不等于 | 否 | 与 === 相反 |
比较的类型转换规则
松散比较 == 的类型转换遵循以下规则:
- 如果两个操作数类型相同,直接比较值。
- 如果一个是
null,另一个转换为null后比较。 - 如果一个是
bool,另一个转换为bool后比较。 - 如果一个是
int,另一个是string,将string转为int后比较。 - 如果一个是
float,另一个是string,将两者转为数字后比较。
语法与代码示例
松散比较的典型陷阱
php
<?php
declare(strict_types=1);
// 陷阱 1:false 与各种 falsy 值
var_dump(false == null); // bool(true) — 容易混淆
var_dump(false == 0); // bool(true)
var_dump(false == ""); // bool(true)
var_dump(false == "0"); // bool(true)
var_dump(false == []); // bool(true)
// 陷阱 2:0 与字符串
var_dump(0 == "0"); // bool(true)
var_dump(0 == ""); // PHP 8: false; PHP 7: true
var_dump(0 == "hello"); // PHP 8: false; PHP 7: true(危险!)
var_dump(0 == "abc"); // PHP 8: false; PHP 7: true
// 陷阱 3:null 与各种值
var_dump(null == ""); // bool(true)
var_dump(null == 0); // bool(true)
var_dump(null == false); // bool(true)
var_dump(null == "0"); // bool(false)(PHP 8)严格比较的正确用法
php
<?php
declare(strict_types=1);
// 严格比较不会进行类型转换
var_dump(null === null); // bool(true)
var_dump(false === false); // bool(true)
var_dump(0 === 0); // bool(true)
var_dump("" === ""); // bool(true)
// 类型不同,严格比较永远为 false
var_dump(0 === false); // bool(false) — 与 == 不同!
var_dump(0 === "0"); // bool(false)
var_dump("" === false); // bool(false)
var_dump(null === false); // bool(false)
var_dump(null === 0); // bool(false)
var_dump(null === ""); // bool(false)switch 语句中的松散比较
php
<?php
declare(strict_types=1);
$value = 0;
switch ($value) {
case "hello":
echo "String match"; // PHP 7 会执行这里!(0 == "hello")
break;
case false:
echo "Boolean match"; // PHP 8 也会执行这里
break;
case null:
echo "Null match";
break;
case 0:
echo "Int match"; // 两者都会执行到
break;
}
::: danger switch 使用松散比较
`switch` 语句使用 `==`(松散比较)而非 `===`。在 PHP 7 中,`case "hello"` 会匹配 `$value = 0`。这是一个非常危险的陷阱。
### match 表达式使用严格比较
```php
<?php
declare(strict_types=1);
$value = 0;
// match 使用 === 严格比较,更安全
$result = match ($value) {
"hello" => "String match",
false => "Boolean match",
null => "Null match",
0 => "Int match",
};
echo $result; // "Int match" — 不会误匹配详细说明
使用 PHP 函数对变量的检测结果
| 表达式 | gettype() | empty() | is_null() | isset() | if($x) |
|---|---|---|---|---|---|
$x = "" | string | true | false | true | false |
$x = null | NULL | true | true | false | false |
$x = [] | array | true | false | true | false |
$x = false | boolean | true | false | true | false |
$x = true | boolean | false | false | true | true |
$x = 1 | integer | false | false | true | true |
$x = 42 | integer | false | false | true | true |
$x = 0 | integer | true | false | true | false |
$x = "1" | string | false | false | true | true |
$x = "0" | string | true | false | true | false |
$x = "php" | string | false | false | true | true |
$x 未定义 | NULL | true | true | false | false |
松散比较 == 完整表
true | false | 1 | 0 | -1 | "1" | "0" | "-1" | null | [] | "php" | "" | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
true | true | false | true | false | true | true | false | true | false | false | true | false |
false | false | true | false | true | false | false | true | false | true | true | false | true |
1 | true | false | true | false | false | true | false | false | false | false | false | false |
0 | false | true | false | true | false | false | true | false | true | false | false* | false* |
-1 | true | false | false | false | true | false | false | true | false | false | false | false |
"1" | true | false | true | false | false | true | false | false | false | false | false | false |
"0" | false | true | false | true | false | false | true | false | false | false | false | false |
"-1" | true | false | false | false | true | false | false | true | false | false | false | false |
null | false | true | false | true | false | false | false | false | true | true | false | true |
[] | false | true | false | false | false | false | false | false | true | true | false | false |
"php" | true | false | false | false* | false | false | false | false | false | false | true | false |
"" | false | true | false | false* | false | false | false | false | true | false | false | true |
带
*的项在 PHP 8.0 之前为true,PHP 8.0+ 改为false。
严格比较 === 完整表
严格比较 === 只有类型和值都相同时才为 true,对角线为 true,其余全部为 false:
true | false | 1 | 0 | "-1" | "1" | "0" | null | [] | "php" | "" | |
|---|---|---|---|---|---|---|---|---|---|---|---|
true | true | false | false | false | false | false | false | false | false | false | false |
false | false | true | false | false | false | false | false | false | false | false | false |
1 | false | false | true | false | false | false | false | false | false | false | false |
0 | false | false | false | true | false | false | false | false | false | false | false |
"-1" | false | false | false | false | true | false | false | false | false | false | false |
"1" | false | false | false | false | false | true | false | false | false | false | false |
"0" | false | false | false | false | false | false | true | false | false | false | false |
null | false | false | false | false | false | false | false | true | false | false | false |
[] | false | false | false | false | false | false | false | false | true | false | false |
"php" | false | false | false | false | false | false | false | false | false | true | false |
"" | false | false | false | false | false | false | false | false | false | false | true |
PHP 8.0 比较行为变更
PHP 8.0 修复了多个不合理的比较行为:
php
<?php
declare(strict_types=1);
// PHP 8.0 修复的行为
var_dump(0 == "foo"); // PHP 7: true; PHP 8: false
var_dump(0 == ""); // PHP 7: true; PHP 8: false
var_dump(0 == "php"); // PHP 7: true; PHP 8: false
var_dump("" == "0"); // PHP 7: true; PHP 8: false
var_dump(0 == "0"); // true(不变)
var_dump("0" == null); // PHP 7: true; PHP 8: false
var_dump("" == null); // true(不变)PHP 8.0 是一个重要的分水岭
PHP 8.0 之前的松散比较存在大量不直觉的行为。如果你的项目运行在 PHP 8.0+,许多经典的比较陷阱已被修复,但仍建议使用严格比较。
实战示例
安全的表单输入验证
php
<?php
declare(strict_types=1);
function validateFormData(array $data): array
{
$errors = [];
// 不要使用 == 比较,使用严格比较或空字符串检查
$username = $data['username'] ?? '';
if ($username === '') {
$errors[] = 'Username is required';
}
// 不要用 == null 检查,用 === null 或 empty()
$age = $data['age'] ?? null;
if ($age === null) {
$errors[] = 'Age is required';
} elseif (!is_numeric($age)) {
$errors[] = 'Age must be a number';
}
// 使用 filter_var 进行类型验证
$email = $data['email'] ?? '';
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[] = 'Invalid email address';
}
return $errors;
}防御性比较工具
php
<?php
declare(strict_types=1);
class Compare
{
/**
* 安全的等于比较 —— 始终使用严格比较
*/
public static function equal(mixed $a, mixed $b): bool
{
return $a === $b;
}
/**
* 检查值是否为"空"(null、空字符串、空数组、false、0)
* 但区分 0 和 "0" 的不同场景
*/
public static function isEmpty(mixed $value): bool
{
if ($value === null) {
return true;
}
if ($value === '') {
return true;
}
if ($value === []) {
return true;
}
return false;
}
/**
* 检查值是否为"空白"(空字符串或仅含空白字符)
*/
public static function isBlank(mixed $value): bool
{
if ($value === null) {
return true;
}
if (is_string($value) && trim($value) === '') {
return true;
}
return false;
}
/**
* 安全的数组键检查 —— 区分 null 值和缺失键
*/
public static function arrayHasKey(string $key, array $array): bool
{
return array_key_exists($key, $array);
}
}
// 使用示例
$data = ['name' => 'Alice', 'email' => '', 'age' => 0];
// isset 会把空字符串和 0 当作 "已设置"
var_dump(isset($data['email'])); // true
var_dump(isset($data['age'])); // true
// array_key_exists 只检查键是否存在
var_dump(array_key_exists('email', $data)); // true
// 区分 null 值和缺失键
$data2 = ['name' => null];
var_dump(isset($data2['name'])); // false
var_dump(array_key_exists('name', $data2)); // truespaceship 运算符
php
<?php
declare(strict_types=1);
// <=> 太空船运算符:返回 -1, 0, 1
// 用于比较排序
$values = [3, 1, 4, 1, 5, 9, 2, 6];
// 数值排序
usort($values, fn($a, $b) => $a <=> $b);
echo implode(', ', $values); // 1, 1, 2, 3, 4, 5, 6, 9
// 注意:<=> 也使用松散比较的规则
var_dump(0 <=> "foo"); // PHP 7: 0; PHP 8: -1注意事项
最危险的 5 个比较陷阱
| 排名 | 陷阱 | PHP 7 | PHP 8 | 建议 |
|---|---|---|---|---|
| 1 | 0 == "php" | true | false | 使用 === |
| 2 | "" == null | true | true | 使用 === |
| 3 | 0 == false | true | true | 使用 === |
| 4 | "0" == false | true | true | 使用 === |
| 5 | "" == 0 | true | false | 使用 === |
PHP 比较不具备传递性
PHP 的松散比较不具备数学上的传递性,这是一个已知的语言缺陷:
php
<?php
declare(strict_types=1);
// 传递性失败的经典案例
var_dump("php" == 0); // PHP 7: true; PHP 8: false
var_dump(0 == null); // true
var_dump("php" == null); // false
// A == B 且 B == C,但 A != CNAN 的特殊行为
NAN(Not a Number)与任何值(包括自身)比较都是 false,除非与 true 比较:
php
<?php
declare(strict_types=1);
$val = NAN;
var_dump($val == $val); // false
var_dump($val === $val); // false
var_dump($val == true); // true(NAN 是 truthy)
// 正确的 NAN 检查方式
var_dump(is_nan($val)); // true最佳实践
始终使用
===进行比较:除非你明确知道==的类型转换规则并且需要这种行为,否则使用===。用
match替代switch:match使用严格比较===,避免了switch使用松散比较的陷阱。用
array_key_exists()检查键是否存在:isset()在值为null时返回false,不能区分"键不存在"和"值为 null"。用
=== null而非== null:== null会将""、0、false也视为匹配。用
filter_var()验证输入:不要依赖松散比较来验证用户输入的类型。升级到 PHP 8.0+:PHP 8.0 修复了大量不合理的比较行为,是类型安全的重要里程碑。