目錄

isset() 與 empty() 的差異--延伸題

九月初曾經寫過一篇文章是介紹 isset() 與 empty() 的差異,這篇來介紹延伸比較:

  1. 跟 isset() 幾乎一樣的 ??Null coalescing operator)。
  2. 與 array_key_exists() 很像的 property_exists()

Coalescing 這個單字有合併的意思,這運算子的用法有包含了 null 的判斷,故稱為 Null coalescing operator。下面是簡單的測試結果:

Psy Shell v0.10.8 (PHP 8.0.11 — cli) by Justin Hileman
>>> $t = $v ?? 'foo'
=> "foo"
>>> $v = null
=> null
>>> $t = $v ?? 'foo'
=> "foo"
>>> $v = 0
=> 0
>>> $t = $v ?? 'foo'
=> 0
>>> 

從這個測試可以知道,?? 它可以判斷變數未設置,同時也判斷變數是否為 null,這個結果與 isset() 相同,可以參考先前的文章了解 isset() 的詳細說明。

來看一下原始碼為何:

<?php

$v = null;

$result = isset($v) ? $v : 'foo';
$result = is_null($v) ? 'foo' : $v;
$result = null === $v ? 'foo' : $v;
$result = $v ?? 'foo';

接著 dump opcode:

function name:  (null)
number of ops:  28
compiled vars:  !0 = $v, !1 = $result
line      #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
    3     0  E >   EXT_STMT                                                 
          1        ASSIGN                                                   !0, null
    5     2        EXT_STMT                                                 
          3        ISSET_ISEMPTY_CV                                         !0
          4      > JMPZ                                                     ~3, ->7
          5    >   QM_ASSIGN                                        ~4      !0
          6      > JMP                                                      ->8
          7    >   QM_ASSIGN                                        ~4      'foo'
          8    >   ASSIGN                                                   !1, ~4
    6     9        EXT_STMT                                                 
         10        TYPE_CHECK                                    2          !0
         11      > JMPZ                                                     ~6, ->14
         12    >   QM_ASSIGN                                        ~7      'foo'
         13      > JMP                                                      ->15
         14    >   QM_ASSIGN                                        ~7      !0
         15    >   ASSIGN                                                   !1, ~7
    7    16        EXT_STMT                                                 
         17        TYPE_CHECK                                    2          !0
         18      > JMPZ                                                     ~9, ->21
         19    >   QM_ASSIGN                                        ~10     'foo'
         20      > JMP                                                      ->22
         21    >   QM_ASSIGN                                        ~10     !0
         22    >   ASSIGN                                                   !1, ~10
    8    23        EXT_STMT                                                 
         24        COALESCE                                         ~12     !0
         25        QM_ASSIGN                                        ~12     'foo'
         26        ASSIGN                                                   !1, ~12
    9    27      > RETURN                                                   1

這裡看到 ??(line 8)用了 COALESCE 以及跟三元運算子相同的 QM_ASSIGN,最後再 ASSIGN 變數結束。 雖然 opcode 是少的,但它應該需要做很多事,包含型別的判斷等,因此效能可能比較差一點。

接著來實測:

+---------------------+----------------+-----------+---------+---------+---------+--------+---------+
| benchmark           | subject        | memory    | min     | max     | mode    | rstdev | stdev   |
+---------------------+----------------+-----------+---------+---------+---------+--------+---------+
| ConditionNullBench  | bench ()       | 969.752kb | 0.466μs | 0.509μs | 0.490μs | ±2.88% | 0.014μs |
| NullCoalescingBench | bench ()       | 969.752kb | 0.447μs | 0.567μs | 0.467μs | ±9.15% | 0.045μs |
| IsNullBench         | bench ()       | 969.784kb | 0.450μs | 0.537μs | 0.460μs | ±6.88% | 0.033μs |
| IssetBench          | bench ()       | 969.784kb | 0.440μs | 0.473μs | 0.463μs | ±2.33% | 0.011μs |
+---------------------+----------------+-----------+---------+---------+---------+--------+---------+

實際結果發現,其實沒有什麼差異,就放心大膽的用下去吧!

測試原始碼參考

參考這篇文章,發現 property_exists() 應該跟 array_key_exists() 很像,因此拿來用 PHPUnit 試一下是否是一樣的:

$actual = property_exists((object)$arr, 'foo');

$this->assertSame($expected, $actual);

這裡的 $arr 是之前測試 array_key_exists() 的 case,用了 (object) 可以將 ['foo' => 'bar'] 轉成 stdClass 物件,並包含了 foo 的屬性。

單元測試測完結果正確,因此 property_exists() 與 array_key_exists() 的真值表是一樣的沒錯!

而效能部分就不測了,因為以前曾試過 array 存取的速度跟 object 不同,因此考量的點主要在 array 與 object 的選擇,這未來有機會再另外寫一篇文章。