如果左侧的值存在且不为 null,?? 就返回它,否则返回右侧的值:$name = $_GET['name'] ?? 'Guest';。和直接读取键不同,当变量或数组键不存在时,它从不发出警告。
<?php
$name = $_GET["name"] ?? "Guest";
$lang = $_GET["lang"] ?? "en";
echo "<p>Hello, " . htmlspecialchars($name) . " (language: " . htmlspecialchars($lang) . ")</p>";
echo '<p><a href="?name=Ada">?name=Ada</a> | <a href="?name=Linus&lang=fi">?name=Linus&lang=fi</a> | <a href="?">no query</a></p>';
第一次运行时没有查询字符串,所以两个默认值都会被使用。在“页面”标签页中点击一个链接,脚本会再次运行,这时 $_GET 已经有了数据。如果第一次运行时不用 ?? 直接读取 $_GET["name"],在常规的 PHP 环境中会打印 Warning: Undefined array key "name"。关于请求数据的更多内容,请参阅 GET和_POST 页面。
?? 是 isset() 检查的简写
$a ?? $b 的含义与 isset($a) ? $a : $b 完全相同。它适用于变量、任意深度的数组键和对象属性,并且会在遇到第一个缺失的部分时安静地停下。
<?php
$config = [
"db" => ["host" => "localhost", "port" => null],
];
// The long way
$host = isset($config["db"]["host"]) ? $config["db"]["host"] : "127.0.0.1";
// The same with ??
$host = $config["db"]["host"] ?? "127.0.0.1";
$port = $config["db"]["port"] ?? 3306; // null, so default
$user = $config["db"]["user"] ?? "root"; // missing key
$cache = $config["cache"]["ttl"] ?? 60; // missing parent key too
$debug = $undefinedVariable ?? false; // missing variable
var_dump($host, $port, $user, $cache, $debug);
这里的检查和 isset 所做的完全一样,所以一个存在但值为 null 的键也会取默认值。
用 ?? 链式尝试多个来源
?? 是右结合的,你可以想串多少个就串多少个。PHP 会返回第一个已设置且不为 null 的操作数。
<?php
$fromQuery = null;
$fromCookie = null;
$fromProfile = "fr";
$lang = $fromQuery ?? $fromCookie ?? $fromProfile ?? "en";
echo $lang, "\n";
$settings = ["theme" => null];
$theme = $settings["theme"] ?? $settings["defaultTheme"] ?? "light";
echo $theme, "\n";
??= 只在为 null 时赋值
只有当 $a 未设置或为 null 时,$a ??= $b 才会把 $b 存进 $a。这是填充默认值,或者只计算一次然后重复使用某个结果的最简写法。
<?php
$options = ["color" => "red", "size" => null];
$options["color"] ??= "blue"; // already set, unchanged
$options["size"] ??= "M"; // null, so assigned
$options["qty"] ??= 1; // missing, so assigned
print_r($options);
// Compute once, reuse afterwards
function slowSquare(int $n): int {
echo "computing $n\n";
return $n * $n;
}
$cache = [];
foreach ([4, 4, 7, 4] as $n) {
$cache[$n] ??= slowSquare($n);
echo "$n squared is {$cache[$n]}\n";
}
??= 右侧的表达式只在真正赋值时才会求值,所以 computing 4 只出现了一次。
?? 与 ?:(简写三元运算符)
?? 检查的是 null,?: 检查的是真假值。遇到 0、"" 和 "0" 这样的值时,它们会给出不同的结果:
<?php
$inputs = ["qty" => "0", "note" => "", "coupon" => null];
foreach (["qty", "note", "coupon", "missing"] as $key) {
$viaCoalesce = $inputs[$key] ?? "default";
$viaElvis = ($inputs[$key] ?? null) ?: "default";
printf("%-8s ?? => %-10s ?: => %s\n",
$key, var_export($viaCoalesce, true), var_export($viaElvis, true));
}
数量为 "0" 是真实的输入:?? 会保留它,?: 则会把它替换掉。单独使用 ?: 会因为键不存在而发出警告,所以代码中把它写成了 ($inputs[$key] ?? null) ?: ...。三元运算符页面 介绍了 ?: 本身。
空安全运算符 ?->
?-> 是同一思路的对象版本,在 PHP 8 中加入。如果左侧的对象是 null,整条链就会停下并返回 null,而不是抛出像 Call to a member function getAddress() on null 这样的 Error。在末尾加上 ?? 即可提供默认值。
<?php
class Address {
public function __construct(public string $city) {}
}
class User {
public function __construct(public ?Address $address = null) {}
public function getAddress(): ?Address { return $this->address; }
}
function findUser(int $id): ?User {
return match ($id) {
1 => new User(new Address("Paris")),
2 => new User(),
default => null,
};
}
foreach ([1, 2, 3] as $id) {
$city = findUser($id)?->getAddress()?->city ?? "unknown";
echo "user $id: $city\n";
}
用户 2 存在但没有地址,用户 3 则根本不存在。两者都会落到 ?? "unknown" 默认值上,而不会出错。
常见错误:?? 的优先级很低
?? 的结合力比 . 和算术运算符都要弱。所以在 "Count: " . $count ?? 0 中,PHP 会先构建字符串 "Count: " . $count,它永远不会是 null,默认值也就永远用不上。
<?php
$count = null;
echo "Count: " . $count ?? 0, "\n"; // the ?? applies to the whole string
echo "Count: " . ($count ?? 0), "\n"; // what was meant
只要 ?? 表达式位于一个更大的表达式中,就用括号把它括起来。