类型转换(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 在以下上下文中会自动进行类型转换:
- 数字上下文:算术运算
- 字符串上下文:
echo、字符串拼接 - 逻辑上下文:条件语句、逻辑运算
- 整数和字符串上下文:位运算
- 比较上下文:比较运算符
- 函数上下文:类型声明参数/返回值
语法与代码示例
显式类型转换
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 的规则
| 源类型 | 转换结果 | 说明 |
|---|---|---|
bool | true -> 1, false -> 0 | - |
float | 截断小数部分 | 3.99 -> 3 |
string | 前导数字部分 | "42abc" -> 42 |
null | 0 | - |
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 conversionPHP 8.0 的变更
PHP 8.0 修改了一些类型转换行为:
| 行为 | PHP 7 | PHP 8 |
|---|---|---|
"0" == "" | true | false |
"0" == null | true | false |
"0" == false | true | true(不变) |
| 数字字符串 + 非数字字符串 | 0 + Warning | TypeError |
"php" == 0 | true | false |
"" == 0 | true | false |
已弃用的转换
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)最佳实践
始终使用
declare(strict_types=1):在所有 PHP 文件中启用严格模式,避免意外的隐式类型转换。显式转换优于隐式转换:当需要类型转换时,使用
(int)、(string)等显式运算符,让意图更明确。使用
===而非==:严格比较运算符不会进行类型转换,避免松散比较的陷阱。验证输入后再转换:不要盲目转换,先使用
is_numeric()、is_string()等函数验证。避免依赖隐式转换:在非严格模式下,隐式转换可能导致难以发现的 bug。
使用
settype()仅在必要时:settype()会修改变量本身,大多数场景下显式转换(int)更合适。