isset() 與 empty() 的差異
PHP 的 isset() 和 empty() 是解析變數常用的語言結構,兩者用法很接近,有時一不小心就會誤用。
這是一個老掉牙的主題,隨便 Google 都一堆比較文,官網也有描述它們的差異。但我還是決定重覆造輪子,主要是想把 array_key_exists() 函式拿來一起討論。
同場加映:??(Null coalescing operator)與 property_exists() 函式的比較。
2021/10/3 補充:舊版 PHP 的 isset() 其他情境。
先來看一下這三個功能在使用上的差異,為什麼用「功能」來描述它們?因為 isset() 和 empty() 是語言結構(Language construct),而 array_key_exists() 是函式(function),所以用「功能」來統稱它們。
以下程式碼區塊為 Psy Shell 實驗複制下來的,環境為:
Psy Shell v0.10.8 (PHP 8.0.9 — cli) by Justin Hileman |
isset()
isset() 語意上是在判斷變數是否被設置,所以只能傳變數,而不能直接傳值,直接傳值會拋出 Fatal error:
>>> isset(0) |
會回傳 true 的情境包括 0、空字串、空陣列等,因為這些都是變數已存在的情境;相對地,回傳 false 的情境只有兩個:
- 變數未設置,類似 Javascript 的 undefined(註一)
- 變數值是
null
註一:根據前輩的經驗指出,舊版 PHP 無法正常處理變數未設置的狀況而出錯,但不知道是哪一版。查過官網也沒有相關資訊,不過依我的經驗,從 PHP 5.3 開始都是如文章所述。
當嘗試去使用一個未設置的變數時,會丟出一個 Warning 的訊息,並且回傳 null,而 isset() 可以把 Warning 的訊息吃掉:
>>> $a |
再來有個特別的狀況:is_null() 官網有拿來一起比較,注意看會發現,isset() 的真值表剛好跟 is_null() 完全相反,它們有三個地方不同:
- isset() 是語法結構;is_null() 是函式,
所以 isset() 會比較快一點但推測 compiler 已有做最佳化,測試的結果是差不多的。 - isset() 在變數沒定義時,不會出任何錯誤;is_null() 會出現 PHP Warning
- isset() 必須要傳變數,不能傳實字常數(literal constants),這在開頭有說明;is_null() 可以傳變數與實字常數,如
is_null(null)
而以 isset() 與 is_null() 來說,大多數情境都是用 isset() 居多,主要是當變數沒定義時,可以確保不會出錯;另一方面是因為 is_null($value)
其實跟 $value === null
意義一樣,後者的效能跟 isset() 差不多,因為呼叫函式還會多一層 overhead。
當變數可能沒定義的情境,建議使用 isset(),可以確保不會噴 Warning 訊息;而想確認變數是否為 null 的話,則建議使用 $value === null
,因為 is_null()
在 namespace 下使用會變慢,需要加 use function is_null;
才會正常,但一般都會忘了加,如同本篇文章一開始做測試一樣。
+------------------+-----------------------+-----------+---------+---------+---------+---------+---------+ |
測試程式: https://github.com/MilesChou/php-notice/blob/master/benchmarks/IssetIsNullBench.php
9/1 更新:從 opcodes 可以確定
is_null($v)
效能跟$v === null
效能相同,因此上文有針對此部分調整說明。
empty()
empty() 語意上是在看變數是否是空的,在 PHP 5.5 之前是不能傳值的,5.5 開始可以接受傳值。
會回傳 true 的情境如變數未設定,這跟 isset() 剛好相反的;而如果是在判斷未定義這件事的話,isset() 和 empty() 的效能是差不多的。
效能可參考最下面的附表一:效能比較
但問題在於,empty() 當 0、空字串、空陣列等情境(可參考官網),也會回傳 true,因為它們確實是「空」的,這些判斷方法就跟 isset() 不一樣了。
empty() 與 isset() 雖然這兩個語言結構都支援未定義變數,但對於判斷資料型態是否為「空」,用法完全不同,因此如果要針對資料型態資料做空的判斷時,才需要使用 empty();判斷變數是否有設值,還是要用 isset()。
變數定義相較是靜態的,開發者如果有配合 IDE 或靜態分析去改善寫法的話,undefined 的情境通常都能抓得到,不大需要用到 isset(),需要這個判斷通常會是 array。
array_key_exists()
isset() 可以確認 array 的一個 key 是否有設值,而有另一個函式:array_key_exists() 與 isset() 非常像。差異直接使用舉例說明,當 array 某個元素是 null 的時候:
- isset() 會回傳 false
- array_key_exists() 會回傳 true
可參考最下面的附表二:真值表
因此這兩個功能的用法又有點不大一樣,在實作 key 是否存在的判斷時,記得它們在值是 null 的情境下,會有不同的回傳值。
總結
isset() 和 empty() 在 IDE 與靜態分析的協助下,一般不大需要對變數做判斷,因此主要會是應用在 array 的場景。如果是 array 場景,會有另一個函式 array_key_exists() 可以參考使用,這三個功能在 array 的應用場景如下:
- empty() 用在對資料型態是否為空的判斷
- isset() 可判斷 array key 是否已定義,而值是 null 的時候會是未定義(false)
- array_key_exists() 可判斷 array key 是否存在,當值是 null 的時候會是存在(true)
最後分享一下,最近常見到兩個類似的寫法如下:
if (isset($arr['foo']) || !empty($arr['foo'])) { |
看完本篇說明後可以知道,跟下面這個寫法是一模一樣的:
if (isset($arr['foo'])) { |
NOT 運算對調,其實也是類似的,只是結果變成跟 empty() 相同:
if (!isset($arr['foo']) || empty($arr['foo'])) { |
跟下面這個寫法一模一樣:
if (empty($arr['foo'])) { |
測試程式碼: https://github.com/MilesChou/php-notice/blob/master/tests/IssetEmptyTest.php
同時用了兩個判斷的人,建議還是要好好思考到底需求為何,到底是要 isset() 還是 empty(),不然會讓閱讀程式的人(或未來的自己)搞不懂要做什麼。
附表一:效能比較
效能測試了一下,array_key_exists() 因為是函式,所以比較慢;isset() 和 empty() 則差不多:
+---------------------+------------------------------------+-----------+---------+---------+---------+--------+---------+ |
測試程式連結: https://github.com/MilesChou/php-notice/blob/master/benchmarks/ArrayKeyExistsBench.php
附表二:真值表
2021/9/30 新增
property_exists()
函式的真值表
$v 的內容 | isset($v['foo']) |
empty($v['foo']) |
array_key_exists('foo', $v) |
property_exists((object)$v, 'foo') |
---|---|---|---|---|
[] |
false | true | false | false |
['foo' => null] |
false | true | true | true |
['foo' => ''] |
true | true | true | true |
['foo' => []] |
true | true | true | true |
['foo' => ['a', 'b']] |
true | false | true | true |
['foo' => false] |
true | true | true | true |
['foo' => true] |
true | false | true | true |
['foo' => 1] |
true | false | true | true |
['foo' => 0] |
true | true | true | true |
['foo' => -1] |
true | false | true | true |
['foo' => "1"] |
true | false | true | true |
['foo' => "0"] |
true | true | true | true |
['foo' => "-1"] |
true | false | true | true |
['foo' => "php"] |
true | false | true | true |
['foo' => "true"] |
true | false | true | true |
['foo' => "false"] |
true | false | true | true |