PHP短三元运算符(“Elvis”)和空合并运算符的比较

559

有人能够解释一下PHP中的三元运算符简写(?:)和null 合并运算符(??)之间的区别吗?

它们在什么情况下会表现出不同的行为,在什么情况下又是相同的(如果有的话)?

$a ?: $b

对比。

$a ?? $b

3
一个好的测试方法是使用交互模式下的php控制台(php -a)。然后设置ini_set('error_reporting', 'E_ALL')ini_set('display_errors', 'on')。接着你可以尝试var_dump($var ?? 'default')),看看在此之前设置任何类型的值会发生什么。 - St3an
很难通过谷歌搜索找到:文档可以省略三元运算符的中间部分。表达式expr1 ?: expr3如果expr1为真,则返回expr1,否则返回expr3 - Basj
15个回答

578
  • Elvis ?: 如果第一个参数包含"true-ish"值(参见Loose comparisons with ==表的第一行,了解哪些值被认为是松散相等于true),则返回第一个参数。否则返回第二个参数。

      $result = $var ?: 'default';
      // 是以下代码的缩写
      $result = $var ? $var : 'default';
    
  • Null 合并运算符 ?? 如果第一个参数已设置且不为 null,则返回第一个参数。否则返回第二个参数。

      $result = $var ?? 'default';
      // 是以下代码的缩写 
      $result = isset($var) ? $var : 'default';
    

当你的第一个参数为null时,它们基本上是相同的,除了当你有一个未定义的变量时,null合并不会输出E_NOTICEPHP 7.0 migration docs中有这样的说法:

空合并运算符(??)被添加为语法糖,用于通常需要与isset()一起使用三元运算符的情况。如果第一个操作数存在且不为NULL,则返回第一个操作数;否则返回第二个操作数。

以下是一些示例代码以演示此内容:

<?php

$a = null;

print $a ?? 'b'; // b
print "\n";

print $a ?: 'b'; // b
print "\n";

print $c ?? 'a'; // a
print "\n";

print $c ?: 'a'; // Notice: Undefined variable: c in /in/apAIb on line 14
print "\n";

$b = array('a' => null);

print $b['a'] ?? 'd'; // d
print "\n";

print $b['a'] ?: 'd'; // d
print "\n";

print $b['c'] ?? 'e'; // e
print "\n";

print $b['c'] ?: 'e'; // Notice: Undefined index: c in /in/apAIb on line 33
print "\n";

有提示的行是我使用三元运算符而不是空合并运算符的地方。然而,即使有提示,PHP也会返回相同的响应。

执行代码:https://3v4l.org/McavC

当然,这总是假设第一个参数是null。一旦它不再是null,那么你就会发现差异在于??运算符总是返回第一个参数,而?:简写只有在第一个参数为真时才返回,这取决于PHP如何将事物强制转换为布尔值

所以:

$a = false ?? 'f'; // false
$b = false ?: 'g'; // 'g'

那么$a将等于false$b将等于'g'


20
提示:如果您一直使用 ?? 而非 ?:,但现在需要将代码与 PHP 版本早于 7(例如插件)兼容,则可能希望在代码中的所有位置中将 ?? 替换为 isset($something) ? $something : $something_else。您可以使用 Notepad++ 或 nedit(以及其他编辑器)的查找/替换工具轻松完成此操作,选择正则表达式选项,并在查找字段中插入:“\s(\S+)\s ??”,在替换字段中插入:“isset($1)?$1:$ something_else”,不包括引号(nedit 使用 \1 而不是 $1)。然后全部替换。 - Damian Green
38
这是正确的答案,然而真实性检查是主要区别,应该更加强调。 - mancze
2
@MasterOdin,我对你的回答不满意。两者并不相同,结果也不同。 - Dhairya Lakhera
2
值得注意的是,您可以在链接中使用 ??。例如:$b = []; var_dump($b['a']['b']['c'] ?? 'default'); 或者对于对象 $b = new Foo; var_dump($b->a()->b()->c() ?? 'default'); - Jack B
请注意,使用 $a = []; 时行为也会不同。详情见:https://3v4l.org/iCCa0 - Soullivaneuh

266
在 PHP 交互模式下(终端上的 php -a),执行以下操作。每行的注释显示结果。
var_export (false ?? 'value2');   // false
var_export (true  ?? 'value2');   // true
var_export (null  ?? 'value2');   // value2
var_export (''    ?? 'value2');   // ""
var_export (0     ?? 'value2');   // 0

var_export (false ?: 'value2');   // value2
var_export (true  ?: 'value2');   // true
var_export (null  ?: 'value2');   // value2
var_export (''    ?: 'value2');   // value2
var_export (0     ?: 'value2');   // value2

空合并运算符??

  • ??就像一个“门”,只允许NULL通过
  • 因此,它总是返回第一个参数,除非第一个参数恰好为 NULL
  • 这意味着??(!isset() || is_null())相同

使用??

  • 缩短!isset() || is_null()检查
  • 例如$object = $object ?? new objClassName();

堆叠空合并运算符

        $v = $x ?? $y ?? $z; 

        // This is a sequence of "SET && NOT NULL"s:

        if( $x  &&  !is_null($x) ){ 
            return $x; 
        } else if( $y && !is_null($y) ){ 
            return $y; 
        } else { 
            return $z; 
        }

三目运算符 ?:

  • ?: 就像一个门闸,可以让 任何假值 通过 - 包括 NULL
  • 假值包括:0空字符串NULLfalse!isset()empty()
  • 与旧的三目运算符相同: X ? Y : Z
  • 注意:对于未定义(unset!isset())变量,?: 会抛出 PHP NOTICE

?: 的用法

  • 检查 empty()!isset()is_null()
  • 将类似于 !empty($x) ? $x : $y 的三元操作简化为 $x ?: $y
  • if(!$x) { echo $x; } else { echo $y; } 简化为 echo $x ?: $y

堆叠三元运算符

        echo 0 ?: 1 ?: 2 ?: 3; //1
        echo 1 ?: 0 ?: 3 ?: 2; //1
        echo 2 ?: 1 ?: 0 ?: 3; //2
        echo 3 ?: 2 ?: 1 ?: 0; //3
    
        echo 0 ?: 1 ?: 2 ?: 3; //1
        echo 0 ?: 0 ?: 2 ?: 3; //2
        echo 0 ?: 0 ?: 0 ?: 3; //3

    
        // Source & Credit: http://php.net/manual/en/language.operators.comparison.php#95997
   
        // This is basically a sequence of:

 
        if( truthy ) {}
        else if(truthy ) {}
        else if(truthy ) {}
        ..
        else {}

同时堆叠,我们可以缩短代码:

        if( isset($_GET['name']) && !is_null($_GET['name'])) {
            $name = $_GET['name'];
        } else if( !empty($user_name) ) {
             $name = $user_name; 
        } else {
            $name = 'anonymous';
        }

翻译成中文:

        $name = $_GET['name'] ?? $user_name ?: 'anonymous';

很酷,对吧?:-)


2
优秀,除了一个错误:将if(!$x) { echo $x; } else { echo $y; }缩短为echo $x ?: $y。两者并不相等。条件必须是if($x),而不是否定。它仍然让我学习了一些关于这个新的操作符的知识,所以帖子得到了赞。 - Soul Reaver
1
在 PHP 中,请始终使用 elseif 作为单个单词以符合 PSR-12 编码标准。我知道你只是在演示,但首先检查 isset($_GET['name']) && !is_null($_GET['name']) 是多余的检查。 - mickmackusa
8
这是一个非常好的答案,可以学习这些运算符如何工作,但我希望我永远不必调试同时在一个语句中使用这两个运算符的生产代码! - Liam
1
非常好的解释,但不要错过这一行: “注意:?: 会在未定义(未设置或!isset())变量时抛出PHP NOTICE”,如果您不能保证变量(特别是数组索引)已设置,则仍然必须使用!empty()或isset()(取决于您需要的行为)。 - achasinh

80
如果您像这样使用快捷的三元运算符,当$_GET['username']未设置时,它会导致一个提示:
$val = $_GET['username'] ?: 'default';

所以你需要做类似这样的事情:
$val = isset($_GET['username']) ? $_GET['username'] : 'default';
空值合并运算符相当于上面的语句,如果$_GET['username']未设置或为null,则返回'default':
$val = $_GET['username'] ?? 'default';

请注意,它不会检查真实性。它只检查是否已设置且非空。
您也可以这样做,将返回第一个已定义的(已设置且非null)值:
$val = $input1 ?? $input2 ?? $input3 ?? 'default';

现在这才是一个真正的合并运算符。

为了避免生成通知,应该使用 $var = empty($other_var) ? 'default_value' : $other_var;。请注意,这不包括 ''nullfalse0 - St3an

44
主要区别在于:
  1. Ternary Operator expression expr1 ?: expr3 returns expr1 if expr1 evaluates to TRUE but on the other hand Null Coalescing Operator expression (expr1) ?? (expr2) evaluates to expr1 if expr1 is not NULL

  2. Ternary Operator expr1 ?: expr3 emit a notice if the left-hand side value (expr1) does not exist but on the other hand Null Coalescing Operator (expr1) ?? (expr2) In particular, does not emit a notice if the left-hand side value (expr1) does not exist, just like isset().

  3. TernaryOperator is left associative

    ((true ? 'true' : false) ? 't' : 'f');
    

    Null Coalescing Operator is right associative

    ($a ?? ($b ?? $c));
    
现在让我们通过示例来解释以下两者的区别:
三元运算符 (Ternary Operator) (?:)
$x='';
$value=($x)?:'default';
var_dump($value);

// The above is identical to this if/else statement
if($x){
  $value=$x;
}
else{
  $value='default';
}
var_dump($value);

Null 合并运算符 (??)

$value=($x)??'default';
var_dump($value);

// The above is identical to this if/else statement
if(isset($x)){
  $value=$x;
}
else{
  $value='default';
}
var_dump($value);

这里是一张表格,解释了'??'?:之间的区别和相似之处:

enter image description here

特别说明:空合并运算符和三元运算符是表达式,它们不会计算出一个变量,而是计算出一个表达式的结果。如果您想通过引用返回一个变量,则需要了解这一点。在返回引用的函数中,语句 return $foo ?? $bar; 和 return $var == 42 ? $a : $b; 将不能正常工作,并会发出警告。


请勿在问题或答案中发布文本图片。请编辑您的答案,以文字形式包含此信息。 - miken32

18

当涉及到动态数据处理时,它们的行为方式是不同的。

如果变量为空(''),空合并运算符将把变量视为true,但是简写三元运算符不会。这是需要注意的事情。

$a = NULL;
$c = '';

print $a ?? '1b';
print "\n";

print $a ?: '2b';
print "\n";

print $c ?? '1d';
print "\n";

print $c ?: '2d';
print "\n";

print $e ?? '1f';
print "\n";

print $e ?: '2f';

并输出:

1b
2b

2d
1f

Notice: Undefined variable: e in /in/ZBAa1 on line 21
2f

链接:https://3v4l.org/ZBAa1


对于 PHP 来说,这显然是违反直觉的,因为通常认为空字符串为 false。但是在 ?? 的文档中明确指出:如果第一个操作数存在且不为 NULL,则返回它;否则返回第二个操作数。 - Simon

16

两者都是更长表达式的简写。

?:$a ? $a : $b 的缩写。如果 $a 的值为TRUE,则该表达式将求值为 $a。

??isset($a) ? $a : $b 的缩写。如果 $a 被设置且不为 null,则该表达式将求值为 $a。

当 $a 未定义或为空时,它们的用例重叠。当 $a 未定义时,?? 不会产生 E_NOTICE,但结果相同。当 $a 为空时,结果相同。


8

请在此链接上向下滚动并查看部分内容,它会给你一个如下所示的比较示例:

<?php
/** Fetches the value of $_GET['user'] and returns 'nobody' if it does not exist. **/
$username = $_GET['user'] ?? 'nobody';
/** This is equivalent to: **/
$username = isset($_GET['user']) ? $_GET['user'] : 'nobody';

/** Coalescing can be chained: this will return the first defined value out of $_GET['user'], $_POST['user'], and 'nobody'. **/
$username = $_GET['user'] ?? $_POST['user'] ?? 'nobody';
?>

空合并运算符(??)被添加为与isset()结合使用三元运算符的常见情况的语法糖。如果第一个操作数存在且不为NULL,则返回第一个操作数;否则返回第二个操作数。

实际上,使用合并运算符将自动检查null,而不像三元运算符。


3
请不要考虑使用链式语句……它们像链式三元运算一样难以阅读和理解。 - Mark Baker
@MarkBaker 这只是 PHP 网站上的示例。我将更新帖子以说明不建议使用此方法。 - Script47
7
由于PHP破坏了三元运算符的结合性,因此链接三元运算符很难理解。但这并不适用于null合并运算符,我认为链式null合并是非常容易理解的。 - NikiC
7
我不同意。链式空值合并是一个很棒的特性,如果你理解这个操作符,它并不会使代码难以阅读。在 JavaScript 中,这被广泛使用,一旦人们在 PHP 中熟悉了它,就不应该阻止使用链式空值合并。链式三元运算符非常难读,但空值合并很容易。当你从左到右阅读时,它只是列出下一个应该使用的值。 - earl3s
2
这看起来非常像JS中常见的a || b || c模式,除了PHP的可以用于布尔值(false || 2在JS中是2; false ?? 2在PHP中是false)。 - fregante
1
我不同意你和其他人关于不使用链式编程的观点。这就像说永远不要使用for循环,因为可能不理解它们一样。开发人员/编码人员完全可以自由地使用他们理解的编码标准和实践,即使其他人不理解。就个人而言,我认为链式合并非常类似于switch语句。它返回找到的第一个值(已设置),如果没有找到,则返回最后一个值。 - kurdtpage

8

针对初学者:

空合并运算符(??)

除了 null 值和未定义的值(变量/数组索引/对象属性),其他所有值都为真。

例如:

$array = [];
$object = new stdClass();

var_export (false ?? 'second');                           # false
var_export (true  ?? 'second');                           # true
var_export (null  ?? 'second');                           # 'second'
var_export (''    ?? 'second');                           # ""
var_export ('some text'    ?? 'second');                  # "some text"
var_export (0     ?? 'second');                           # 0
var_export ($undefinedVarible ?? 'second');               # "second"
var_export ($array['undefined_index'] ?? 'second');       # "second"
var_export ($object->undefinedAttribute ?? 'second');     # "second"

这基本上是检查变量(数组索引、对象属性等)是否存在且不为 null。类似于 isset 函数。

三元运算符缩写 (?:)

所有假值(falsenull0、空字符串)都会被视为假,但如果它是未定义的,则也会被视为假,但会抛出一个 Notice

例如:

$array = [];
$object = new stdClass();

var_export (false ?: 'second');                           # "second"
var_export (true  ?: 'second');                           # true
var_export (null  ?: 'second');                           # "second"
var_export (''    ?: 'second');                           # "second"
var_export ('some text'    ?? 'second');                  # "some text"
var_export (0     ?: 'second');                           # "second"
var_export ($undefinedVarible ?: 'second');               # "second" Notice: Undefined variable: ..
var_export ($array['undefined_index'] ?: 'second');       # "second" Notice: Undefined index: ..
var_export ($object->undefinedAttribute ?: 'second');     # "Notice: Undefined index: ..

希望这可以帮到您。

7

实用的简短回答:

尝试:

var_dump('' ?: 'ok');  // prints: ok

var_dump('' ?? 'ok');  // prints empty string

简写三元运算符(?:)将返回左侧的值,如果左侧的值评估为“真实”的内容;否则将返回右侧的值。

空合并运算符(??)将在左侧的值被声明且不为空时返回左侧的值;否则将返回右侧的值。

换句话说,?:测试真实性,??作为isset()的简写。


*注意:如果你想用?:测试一个变量,你必须先确保它已经被初始化/设置,否则PHP会抛出一个E_NOTICE(而??不会)。


3
其他答案深入并给出了很好的解释。对于那些寻求快速答案的人,$a ?: 'fallback' 等价于 $a ? $a : 'fallback',而 $a ?? 'fallback' 等价于 $a = isset($a) ? $a : 'fallback'
主要区别在于左操作数是以下情况之一时:
  • 一个非空的假值(0''false[],...)
  • 一个未定义的变量

上述 ?? 的展开中不应该有 $a =$a ?? 'fallback' 并不会 设置或更改 $a 的值。(它只是返回一个值)。 - Doin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接