Skip to content

类型比较表

概述

PHP 的类型比较系统是许多 bug 的来源。PHP 提供两种比较方式:松散比较(==严格比较(===。松散比较会在比较前自动进行类型转换,而严格比较要求类型和值都相同。理解比较规则是编写安全 PHP 代码的基础。

核心要点

  • == 松散比较:比较前自动类型转换,只比较值。
  • === 严格比较:类型和值都必须相同,不进行类型转换。
  • 0"0"null""false 是 PHP 中最常见的比较陷阱。
  • PHP 8.0 修复了许多不合理的松散比较行为。
  • 始终使用 === 进行比较,除非你明确需要松散比较。
  • :::

基础概念

松散比较 vs 严格比较

运算符名称类型转换比较规则
==松散比较(等于)转换为相同类型后比较值
===严格比较(全等)类型和值都必须相同
!=松散不等于== 相反
!==严格不等于=== 相反

比较的类型转换规则

松散比较 == 的类型转换遵循以下规则:

  1. 如果两个操作数类型相同,直接比较值。
  2. 如果一个是 null,另一个转换为 null 后比较。
  3. 如果一个是 bool,另一个转换为 bool 后比较。
  4. 如果一个是 int,另一个是 string,将 string 转为 int 后比较。
  5. 如果一个是 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 = ""stringtruefalsetruefalse
$x = nullNULLtruetruefalsefalse
$x = []arraytruefalsetruefalse
$x = falsebooleantruefalsetruefalse
$x = truebooleanfalsefalsetruetrue
$x = 1integerfalsefalsetruetrue
$x = 42integerfalsefalsetruetrue
$x = 0integertruefalsetruefalse
$x = "1"stringfalsefalsetruetrue
$x = "0"stringtruefalsetruefalse
$x = "php"stringfalsefalsetruetrue
$x 未定义NULLtruetruefalsefalse

松散比较 == 完整表

truefalse10-1"1""0""-1"null[]"php"""
truetruefalsetruefalsetruetruefalsetruefalsefalsetruefalse
falsefalsetruefalsetruefalsefalsetruefalsetruetruefalsetrue
1truefalsetruefalsefalsetruefalsefalsefalsefalsefalsefalse
0falsetruefalsetruefalsefalsetruefalsetruefalsefalse*false*
-1truefalsefalsefalsetruefalsefalsetruefalsefalsefalsefalse
"1"truefalsetruefalsefalsetruefalsefalsefalsefalsefalsefalse
"0"falsetruefalsetruefalsefalsetruefalsefalsefalsefalsefalse
"-1"truefalsefalsefalsetruefalsefalsetruefalsefalsefalsefalse
nullfalsetruefalsetruefalsefalsefalsefalsetruetruefalsetrue
[]falsetruefalsefalsefalsefalsefalsefalsetruetruefalsefalse
"php"truefalsefalsefalse*falsefalsefalsefalsefalsefalsetruefalse
""falsetruefalsefalse*falsefalsefalsefalsetruefalsefalsetrue

* 的项在 PHP 8.0 之前为 true,PHP 8.0+ 改为 false

严格比较 === 完整表

严格比较 === 只有类型和值都相同时才为 true,对角线为 true,其余全部为 false

truefalse10"-1""1""0"null[]"php"""
truetruefalsefalsefalsefalsefalsefalsefalsefalsefalsefalse
falsefalsetruefalsefalsefalsefalsefalsefalsefalsefalsefalse
1falsefalsetruefalsefalsefalsefalsefalsefalsefalsefalse
0falsefalsefalsetruefalsefalsefalsefalsefalsefalsefalse
"-1"falsefalsefalsefalsetruefalsefalsefalsefalsefalsefalse
"1"falsefalsefalsefalsefalsetruefalsefalsefalsefalsefalse
"0"falsefalsefalsefalsefalsefalsetruefalsefalsefalsefalse
nullfalsefalsefalsefalsefalsefalsefalsetruefalsefalsefalse
[]falsefalsefalsefalsefalsefalsefalsefalsetruefalsefalse
"php"falsefalsefalsefalsefalsefalsefalsefalsefalsetruefalse
""falsefalsefalsefalsefalsefalsefalsefalsefalsefalsetrue

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));  // true

spaceship 运算符

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 7PHP 8建议
10 == "php"truefalse使用 ===
2"" == nulltruetrue使用 ===
30 == falsetruetrue使用 ===
4"0" == falsetruetrue使用 ===
5"" == 0truefalse使用 ===

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 != C

NAN 的特殊行为

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

最佳实践

  1. 始终使用 === 进行比较:除非你明确知道 == 的类型转换规则并且需要这种行为,否则使用 ===

  2. match 替代 switchmatch 使用严格比较 ===,避免了 switch 使用松散比较的陷阱。

  3. array_key_exists() 检查键是否存在isset() 在值为 null 时返回 false,不能区分"键不存在"和"值为 null"。

  4. === null 而非 == null== null 会将 ""0false 也视为匹配。

  5. filter_var() 验证输入:不要依赖松散比较来验证用户输入的类型。

  6. 升级到 PHP 8.0+:PHP 8.0 修复了大量不合理的比较行为,是类型安全的重要里程碑。

参考链接