表达式求值
表达式求值是 PHP 执行代码的核心过程。当 PHP 解析器遇到一个表达式时,它需要确定表达式的类型、计算其值,并在必要时进行类型转换。理解求值规则、运算符优先级和短路求值机制,有助于编写正确且高效的代码。
前置知识
- 已掌握 表达式基础 的概念
- 了解 PHP 的基本数据类型
- 熟悉常用运算符的用法
基础概念
表达式求值是指 PHP 计算表达式并得到一个值的过程。求值涉及多个方面:类型确定、运算执行、类型转换和结果返回。
求值的基本规则
- 从内到外:括号内的表达式先求值
- 根据优先级:不同运算符有不同的优先级
- 根据结合方向:相同优先级的运算符按结合方向求值
- 短路求值:逻辑运算符可能跳过部分表达式
- 类型自动转换:必要时自动转换操作数的类型
Truthy 与 Falsy 值
当表达式用于布尔上下文(如 if 条件、&&/|| 操作)时,PHP 会将值自动转换为布尔值。
Falsy 值
以下值在布尔上下文中被转换为 false:
| 值类型 | Falsy 值 |
|---|---|
| 布尔值 | false |
| 整数 | 0、-0 |
| 浮点数 | 0.0、-0.0 |
| 字符串 | ""(空字符串)、"0" |
| 数组 | [](空数组) |
| null | null |
| SimpleXML | 从空元素创建的对象 |
Truthy 值
所有不是 Falsy 的值都是 Truthy:
php
<?php
declare(strict_types=1);
// Truthy 值示例
$truthyValues = [
true, // 布尔 true
1, -1, 42, // 非零整数
3.14, -0.001, // 非零浮点数
"hello", " ", "false", // 非空字符串("0"除外)
[1, 2], ["key" => "val"], // 非空数组
new stdClass(), // 非空对象
];
// Falsy 值示例
$falsyValues = [
false, // 布尔 false
0, -0, // 零整数
0.0, -0.0, // 零浮点数
"", // 空字符串
"0", // 字符串 "0"
[], // 空数组
null, // null
];空合并运算符 ?? 与 Falsy 值
?? 只检查是否为 null,不会将 Falsy 值视为"无值"。这与 ?:(Elvis 运算符)不同:
php
<?php
declare(strict_types=1);
// ?? 只检查 null
$a = 0 ?? "default"; // 0(0 不是 null)
$b = "" ?? "default"; // ""("" 不是 null)
$c = "0" ?? "default"; // "0"("0" 不是 null)
$d = null ?? "default"; // "default"(null 被替换)
// ?: 检查 truthy/falsy
$e = 0 ?: "default"; // "default"(0 是 falsy)
$f = "" ?: "default"; // "default"("" 是 falsy)
$g = "0" ?: "default"; // "default"("0" 是 falsy)
$h = 1 ?: "default"; // 1(1 是 truthy)
echo "a={$a}, b={$b}, c={$c}, d={$d}\n";
echo "e={$e}, f={$f}, g={$g}, h={$h}\n";类型自动转换规则
转换为布尔值
php
<?php
declare(strict_types=1);
// 以下是 falsy,转换后为 false
var_dump((bool)0); // bool(false)
var_dump((bool)0.0); // bool(false)
var_dump((bool)""); // bool(false)
var_dump((bool)"0"); // bool(false)
var_dump((bool)[]); // bool(false)
var_dump((bool)null); // bool(false)
// 以下是 truthy,转换后为 true
var_dump((bool)1); // bool(true)
var_dump((bool)-1); // bool(true)
var_dump((bool)"hello"); // bool(true)
var_dump((bool)"false"); // bool(true)
var_dump((bool)[1]); // bool(true)转换为整数
php
<?php
declare(strict_types=1);
// 布尔值
var_dump((int)true); // int(1)
var_dump((int)false); // int(0)
// 浮点数(向零截断)
var_dump((int)3.9); // int(3)
var_dump((int)-3.9); // int(-3)
// 字符串
var_dump((int)"42"); // int(42)
var_dump((int)"3.14"); // int(3)
var_dump((int)"abc"); // int(0)
var_dump((int)"42abc"); // int(42)
// null
var_dump((int)null); // int(0)
// 数组
var_dump((int)[5]); // int(1)(非空数组)
var_dump((int)[]); // int(0)(空数组)转换为字符串
php
<?php
declare(strict_types=1);
// 数值
var_dump((string)42); // string(2) "42"
var_dump((string)3.14); // string(4) "3.14"
// 布尔值
var_dump((string)true); // string(1) "1"
var_dump((string)false); // string(0) ""
// null
var_dump((string)null); // string(0) ""
// 数组
var_dump((string)[1, 2]); // string(5) "Array"(直接转换产生警告)详细说明
运算符优先级对求值的影响
优先级决定了表达式中各部分的计算顺序。高优先级的运算符先执行:
php
<?php
declare(strict_types=1);
// 乘法优先级高于加法
echo 1 + 5 * 3 . "\n"; // 16(不是 18)
// 括号改变优先级
echo (1 + 5) * 3 . "\n"; // 18
// 比较运算符的优先级
// && 优先级高于 ||
// 等价于 ($a && $b) || $c
$a = true;
$b = false;
$c = true;
$result = $a && $b || $c;
echo $result ? "true" : "false"; // true
// 等价于 $a && ($b || $c)
$result2 = $a && ($b || $c);
echo $result2 ? "true" : "false"; // trueand/or vs &&/|| 的优先级差异
关键区别
and/or 的优先级低于赋值运算符 =,而 &&/|| 的优先级高于赋值运算符。这是常见的 bug 来源。
php
<?php
declare(strict_types=1);
// && 优先级高于 =
$result = true && false;
var_dump($result); // bool(false)
// 等价于:$result = (true && false)
// and 优先级低于 =
$result = true and false;
var_dump($result); // bool(true)
// 等价于:($result = true) and false
// || 优先级高于 =
$flag = false || true;
var_dump($flag); // bool(true)
// or 优先级低于 =
$flag = false or true;
var_dump($flag); // bool(false)
// 等价于:($flag = false) or true短路求值详解
短路求值(Short-circuit evaluation)是逻辑运算符的重要特性:
php
<?php
declare(strict_types=1);
// || 短路:左侧为 true 则跳过右侧
function logMessage(string $msg): void
{
echo "Logging: {$msg}\n";
}
$debug = true;
$debug || logMessage("Debug mode is OFF");
// logMessage 不会被调用($debug 为 true)
// && 短路:左侧为 false 则跳过右侧
function checkPermission(): bool
{
echo "Checking permission...\n";
return true;
}
$hasAccess = false;
$hasAccess && checkPermission();
// checkPermission 不会被调用($hasAccess 为 false)
// 实用模式:安全访问嵌套数据
$user = ["profile" => ["name" => "Alice"]];
$name = isset($user["profile"]) && isset($user["profile"]["name"])
? $user["profile"]["name"]
: "Unknown";
echo $name . "\n"; // Alice
// PHP 7.0+ 空合并运算符更简洁
$name = $user["profile"]["name"] ?? "Unknown";
echo $name . "\n"; // Alice严格比较 vs 宽松比较
PHP 有两种比较方式:==(宽松比较,允许类型转换)和 ===(严格比较,类型和值都相同):
php
<?php
declare(strict_types=1);
// == 宽松比较(类型转换后比较)
var_dump(0 == false); // bool(true)
var_dump("" == false); // bool(true)
var_dump("0" == false); // bool(true)
var_dump("1" == true); // bool(true)
var_dump(0 == "0"); // bool(true)
var_dump("" == null); // bool(true)
// === 严格比较(不进行类型转换)
var_dump(0 === false); // bool(false)
var_dump("" === false); // bool(false)
var_dump("0" === false); // bool(false)
var_dump("1" === true); // bool(false)
var_dump(0 === "0"); // bool(false)
// !== 非严格不等
var_dump(0 !== "0"); // bool(true)
// <=> 太空船运算符(PHP 7.0+)
// 返回 -1(小于)、0(等于)、1(大于)
echo 1 <=> 2 . "\n"; // -1
echo 2 <=> 2 . "\n"; // 0
echo 3 <=> 2 . "\n"; // 1
echo "a" <=> "b" . "\n"; // -1推荐使用严格比较
在条件判断中始终使用 === 和 !== 进行严格比较,避免因类型自动转换导致的意外行为。
实战示例
安全的类型检测与转换
php
<?php
declare(strict_types=1);
/**
* 安全地获取整数类型的 GET 参数
*/
function getIntParam(string $name, int $default = 0, ?int $min = null, ?int $max = null): int
{
if (!isset($_GET[$name])) {
return $default;
}
// 使用 filter_var 进行严格验证
$value = filter_var($_GET[$name], FILTER_VALIDATE_INT);
if ($value === false) {
return $default;
}
if ($min !== null && $value < $min) {
return $min;
}
if ($max !== null && $value > $max) {
return $max;
}
return $value;
}
// 使用
$page = getIntParam("page", 1, 1, 1000);
$limit = getIntParam("limit", 20, 1, 100);
echo "Page: {$page}, Limit: {$limit}\n";表达式求值在模板中的应用
php
<?php
declare(strict_types=1);
// 模板变量处理:安全的表达式求值
class TemplateRenderer
{
private array $data = [];
public function assign(string $key, mixed $value): void
{
$this->data[$key] = $value;
}
public function render(string $template): string
{
extract($this->data, EXTR_SKIP);
ob_start();
include $template;
return ob_get_clean();
}
public function escape(mixed $value): string
{
return htmlspecialchars((string)$value, ENT_QUOTES, "UTF-8");
}
}
$renderer = new TemplateRenderer();
$renderer->assign("title", "用户管理");
$renderer->assign("count", 42);
$renderer->assign("users", [
["name" => "Alice", "active" => true],
["name" => "Bob", "active" => false],
]);使用 match 表达式(PHP 8.0+)
match 表达式比传统的 switch 更严格和安全,使用 === 严格比较:
php
<?php
declare(strict_types=1);
function getHttpStatusText(int $code): string
{
return match ($code) {
200, 201, 204 => "Success",
301, 302 => "Redirect",
400 => "Bad Request",
401 => "Unauthorized",
403 => "Forbidden",
404 => "Not Found",
500 => "Internal Server Error",
default => "Unknown Status",
};
}
echo getHttpStatusText(200) . "\n"; // Success
echo getHttpStatusText(404) . "\n"; // Not Found
echo getHttpStatusText(999) . "\n"; // Unknown Status
// match 的严格比较特性
$value = "0";
$result = match (true) {
$value === "0" => "string zero",
$value === 0 => "integer zero",
default => "other",
};
echo $result . "\n"; // string zero注意事项
1. PHP 8.0 字符串与数字比较的变更
PHP 8.0 改变了字符串与数字比较的行为。0 == "foo" 在 PHP 7 中为 true(字符串 "foo" 被转换为数字 0),在 PHP 8 中为 false:
php
<?php
declare(strict_types=1);
// PHP 8.0+
var_dump(0 == "foo"); // bool(false)(PHP 7 为 true)
var_dump(0 === "foo"); // bool(false)(所有版本)
// 使用严格比较始终安全
var_dump(0 === "foo"); // bool(false)2. null 与 falsy 值的混淆
php
<?php
declare(strict_types=1);
$value = "";
if (!$value) {
echo "Value is empty\n"; // 会执行(空字符串是 falsy)
}
if (!isset($value)) {
echo "Value is not set\n"; // 不会执行(变量已设置)
}
if ($value === null) {
echo "Value is null\n"; // 不会执行(空字符串不是 null)
}3. 隐式类型转换的风险
php
<?php
declare(strict_types=1);
// 危险:字符串数字比较
var_dump("10" == "1e1"); // bool(true)(科学计数法)
var_dump("10" === "1e1"); // bool(false)(严格比较)
// 危险:十六进制字符串
var_dump("0x10" == 16); // bool(true)(PHP 7)
var_dump("0x10" === 16); // bool(false)(严格比较)
// 安全:使用 filter_var
$number = filter_var("0x10", FILTER_VALIDATE_INT);
var_dump($number); // bool(false)最佳实践
- 始终使用严格比较:用
===和!==替代==和!= - 使用
??替代isset()检查:更简洁的 null 检查 - 区分
??和?::前者检查 null,后者检查 falsy - 使用
match替代switch:PHP 8.0+ 更安全的选择 - 使用
filter_var()验证输入:比手动类型转换更安全 - 明确类型声明:配合
declare(strict_types=1)减少隐式转换 - 括号消除歧义:复杂表达式中使用括号明确优先级
下一节
在掌握了表达式求值的规则之后,接下来将学习运算符的优先级——了解不同运算符之间的优先级关系和结合方向,是编写正确表达式的关键。请阅读 运算符优先级。