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

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

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

Null coalescing operator

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()

參考這篇文章,發現 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 的選擇,這未來有機會再另外寫一篇文章。