Skip to content

类型转换(Type Juggling)

概述

PHP 是弱类型语言,变量的类型由其存储的值决定。PHP 会在不同的上下文中自动进行隐式类型转换(Type Juggling),也支持通过类型转换运算符进行显式转换。理解类型转换规则对于编写健壮的 PHP 代码至关重要。

核心要点

  • PHP 在数字、字符串、逻辑、比较等上下文中自动转换类型。
  • 显式转换使用 (int)(string)(float)(bool)(array)(object) 等运算符。
  • settype() 函数可以修改变量本身的类型。
  • declare(strict_types=1) 仅影响函数参数和返回值的类型检查,不影响隐式类型转换。
  • 类型转换可能导致意外结果,需要特别注意陷阱。
  • :::

基础概念

隐式转换 vs 显式转换

类型说明示例
隐式转换PHP 自动根据上下文转换echo 42 将 int 转为 string
显式转换使用转换运算符强制转换(int) "42" 得到 int 42
settype()修改变量本身的类型settype($var, "int")

PHP 的类型转换上下文

PHP 在以下上下文中会自动进行类型转换:

  1. 数字上下文:算术运算
  2. 字符串上下文echo、字符串拼接
  3. 逻辑上下文:条件语句、逻辑运算
  4. 整数和字符串上下文:位运算
  5. 比较上下文:比较运算符
  6. 函数上下文:类型声明参数/返回值

语法与代码示例

显式类型转换

php
<?php
declare(strict_types=1);

// (int) 转换为整数
$a = (int) "42";           // int(42)
$b = (int) "42abc";       // int(42)
$c = (int) 3.99;          // int(3) —— 截断,不四舍五入
$d = (int) true;          // int(1)
$e = (int) false;         // int(0)
$f = (int) null;          // int(0)

// (float) 转换为浮点数
$g = (float) "3.14";      // float(3.14)
$h = (float) "42";        // float(42.0)
$i = (float) true;        // float(1.0)

// (string) 转换为字符串
$j = (string) 42;         // string(4) "42"
$k = (string) true;        // string(1) "1"
$l = (string) false;       // string(0) ""
$m = (string) null;        // string(0) ""

// (bool) 转换为布尔值
$n = (bool) 1;             // bool(true)
$o = (bool) 0;             // bool(false)
$p = (bool) "";            // bool(false)
$q = (bool) "hello";       // bool(true)
$r = (bool) [];            // bool(false)
$s = (bool) [1, 2];        // bool(true)

// (array) 转换为数组
$t = (array) "hello";      // array(1) { [0] => "hello" }
$u = (array) 42;           // array(1) { [0] => 42 }

// (object) 转换为对象
$v = (object) "hello";     // object(stdClass) { ["scalar"]=> "hello" }
$w = (object) ['a' => 1];  // object(stdClass) { ["a"]=> 1 }

隐式转换规则

php
<?php
declare(strict_types=1);

// 数字上下文:算术运算
$result1 = "10" + 5;      // int(15) —— 字符串 "10" 转为 int
$result2 = "10.5" + 5;     // float(15.5)
$result3 = "abc" + 5;      // PHP 8: TypeError; PHP 7: int(5) with Warning

// 字符串上下文:拼接
$result4 = "Value: " . 42;  // string(9) "Value: 42"

// 逻辑上下文:条件判断
if ("hello") { echo "truthy"; }     // 输出
if ("") { echo "never"; }           // 不输出
if (0) { echo "never"; }            // 不输出
if ([] ) { echo "never"; }          // 不输出

settype() 函数

php
<?php
declare(strict_types=1);

$var = "42";
settype($var, "int");
var_dump($var);  // int(42)

$var = 3.14;
settype($var, "string");
var_dump($var);  // string(4) "3.14"

$var = "hello";
settype($var, "bool");
var_dump($var);  // bool(true)

// settype 返回 bool 表示是否成功
$success = settype($var, "int");
var_dump($success);  // bool(true)

转换为布尔值的规则

以下值转换为 false(所有其他值转换为 true):

php
<?php
declare(strict_types=1);

// 转换为 false 的值(Falsy 值)
$falsyValues = [
    false,       // 布尔 false
    0,           // 整数 0
    0.0,         // 浮点数 0.0
    "",          // 空字符串
    "0",         // 字符串 "0"
    [],          // 空数组
    null,        // null
];

// 简单对象、非空字符串、非零数字等都是 true

"0" 字符串是 falsy

在 PHP 中,字符串 "0" 是 falsy 值,这是一个常见的陷阱。它与整数 0 不同但行为类似。

详细说明

转换为 int 的规则

源类型转换结果说明
booltrue -> 1, false -> 0-
float截断小数部分3.99 -> 3
string前导数字部分"42abc" -> 42
null0-
array元素个数[1,2] -> 2
object不支持(报错)PHP 8: TypeError
php
<?php
declare(strict_types=1);

var_dump((int) "42");          // int(42)
var_dump((int) "42abc");       // int(42)
var_dump((int) "abc42");       // int(0) —— 无前导数字
var_dump((int) "3.14");        // int(3) —— 截断
var_dump((int) "1e3");         // int(1000)
var_dump((int) "0x1A");        // int(0) —— 不识别十六进制字符串
var_dump((int) INF);           // int(0)
var_dump((int) NAN);           // int(0)

转换为 string 的规则

源类型转换结果
bool true"1"
bool false""
int数字字符串
float数字字符串(如 "3.14"
null""
array"Array" + Notice
object调用 __toString() 或报错
php
<?php
declare(strict_types=1);

var_dump((string) true);        // string(1) "1"
var_dump((string) false);       // string(0) ""
var_dump((string) 42);          // string(2) "42"
var_dump((string) 3.14);        // string(4) "3.14"
var_dump((string) null);         // string(0) ""

严格模式下的类型转换

php
<?php
declare(strict_types=1);

function add(int $a, int $b): int
{
    return $a + $b;
}

// 在严格模式下,传入非 int 会直接报错
try {
    add("1", "2");  // TypeError: Argument must be of type int
} catch (TypeError $e) {
    echo "TypeError: " . $e->getMessage() . PHP_EOL;
}

// int 传入 float 参数是唯一例外
function takeFloat(float $a): float
{
    return $a * 2;
}

echo takeFloat(42);  // OK: int 自动转为 float

严格模式例外

即使在 declare(strict_types=1) 下,int 值可以自动转为 float 参数类型。这是唯一的例外。

实战示例

输入数据清理

php
<?php
declare(strict_types=1);

class InputSanitizer
{
    public static function toInt(mixed $value): int
    {
        if (is_numeric($value)) {
            return (int) $value;
        }
        if (is_bool($value)) {
            return $value ? 1 : 0;
        }
        if ($value === null) {
            return 0;
        }
        throw new InvalidArgumentException(
            "Cannot convert to int: " . get_debug_type($value)
        );
    }

    public static function toString(mixed $value): string
    {
        if (is_string($value)) {
            return $value;
        }
        if (is_numeric($value)) {
            return (string) $value;
        }
        if (is_bool($value)) {
            return $value ? '1' : '0';
        }
        if ($value === null) {
            return '';
        }
        if (is_array($value)) {
            return json_encode($value, JSON_UNESCAPED_UNICODE);
        }
        if (is_object($value) && method_exists($value, '__toString')) {
            return (string) $value;
        }
        throw new InvalidArgumentException(
            "Cannot convert to string: " . get_debug_type($value)
        );
    }
}

echo InputSanitizer::toInt("42");      // 42
echo InputSanitizer::toString(3.14); // "3.14"

配置值类型强制转换

php
<?php
declare(strict_types=1);

class Config
{
    public function __construct(private array $data = []) {}

    public function getInt(string $key, int $default = 0): int
    {
        $value = $this->data[$key] ?? $default;
        return is_int($value) ? $value : (int) $value;
    }

    public function getString(string $key, string $default = ''): string
    {
        $value = $this->data[$key] ?? $default;
        return is_string($value) ? $value : (string) $value;
    }

    public function getBool(string $key, bool $default = false): bool
    {
        $value = $this->data[$key] ?? $default;
        return (bool) $value;
    }

    public function getFloat(string $key, float $default = 0.0): float
    {
        $value = $this->data[$key] ?? $default;
        return is_float($value) ? $value : (float) $value;
    }
}

$config = new Config([
    'port' => '8080',
    'debug' => '1',
    'timeout' => '30.5',
]);

echo $config->getInt('port');       // int(8080)
echo $config->getBool('debug');     // bool(true)
echo $config->getFloat('timeout'); // float(30.5)

注意事项

类型转换陷阱

php
<?php
declare(strict_types=1);

// 陷阱 1:字符串与数字比较
var_dump("0" == false);    // bool(true)
var_dump("0" == null);     // bool(false)
var_dump("" == null);       // bool(true)

// 陷阱 2:空数组与 null
var_dump([] == null);       // bool(true)
var_dump([] === null);      // bool(false)

// 陷阱 3:数字字符串截断
var_dump((int) "9223372036854775808");  // 平台上溢出

// 陷阱 4:转换优先级
var_dump("10" + "5");       // int(15)
var_dump("10" . "5");       // string(2) "105"
var_dump("10" == "10.0");   // PHP 8: bool(false)(注意 8.0 变更)

// 陷阱 5:array 转换为 string
$array = [1, 2];
// echo $array;  // Notice: Array to string conversion

PHP 8.0 的变更

PHP 8.0 修改了一些类型转换行为:

行为PHP 7PHP 8
"0" == ""truefalse
"0" == nulltruefalse
"0" == falsetruetrue(不变)
数字字符串 + 非数字字符串0 + WarningTypeError
"php" == 0truefalse
"" == 0truefalse

已弃用的转换

php
<?php
declare(strict_types=1);

// (unset) 已在 PHP 8.0 移除
// $a = (unset) $b;  // PHP 7.2 弃用,PHP 8.0 移除

// 类型别名已弃用(PHP 8.5)
// (integer) -> 使用 (int)
// (boolean) -> 使用 (bool)
// (double)/(real) -> 使用 (float)
// (binary) -> 使用 (string)

最佳实践

  1. 始终使用 declare(strict_types=1):在所有 PHP 文件中启用严格模式,避免意外的隐式类型转换。

  2. 显式转换优于隐式转换:当需要类型转换时,使用 (int)(string) 等显式运算符,让意图更明确。

  3. 使用 === 而非 ==:严格比较运算符不会进行类型转换,避免松散比较的陷阱。

  4. 验证输入后再转换:不要盲目转换,先使用 is_numeric()is_string() 等函数验证。

  5. 避免依赖隐式转换:在非严格模式下,隐式转换可能导致难以发现的 bug。

  6. 使用 settype() 仅在必要时settype() 会修改变量本身,大多数场景下显式转换 (int) 更合适。

参考链接