PHP欄位明明存在卻判定沒有:isset與array_key_exists怎麼分

表單送來一個欄位,值剛好是null;後端卻回報「沒有這個欄位」。遇到這種情況,先看程式是不是用了isset()。它同時要求變數存在、值不是null,不能單靠結果判斷陣列裡有沒有這個鍵。要區分「沒傳」與「明確傳null」,應先用array_key_exists()判斷鍵,再處理值。

這個差別常出現在設定更新、資料匯入及API部分更新。例如使用者要清除副標題,系統把null當成清空指令;若後端把null視為未提供,就會保留舊副標題。本文用普通PHP陣列拆開這兩種情況,讓判斷對應到你真正要處理的動作。

先把「有鍵」與「有值」分開看

假設收到的資料是['subtitle' => null]。subtitle這個鍵確實在陣列裡,但它沒有非null的值。array_key_exists('subtitle', $input)會得到true,isset($input['subtitle'])則得到false。兩個結果都符合PHP的規則,只是它們回答不同的問題。

如果資料是空陣列,兩種檢查都得到false。也就是說,只看isset的false,無法知道欄位缺席,還是欄位存在但值為null。這不是資料自動被刪掉,也不是PHP沒有讀到請求;是你的檢查方式把兩種狀態合在一起。

還要留意空字串、數字0及布林false。這些值都不是null,所以isset會得到true。把isset當成「有填內容」也容易出錯:空字串可以通過isset,卻未必符合你的表單規則。鍵是否存在、值是否可接受,應分成兩個判斷。

用五組資料確認結果

建立一個獨立的case.php,貼上以下程式。它只檢查記憶體裡的陣列,不連接資料庫,也不會修改網站設定。執行前確認你使用的PHP命令指向要測試的版本。

<?php
$cases = [
    'missing' => [],
    'null' => ['subtitle' => null],
    'empty' => ['subtitle' => ''],
    'zero' => ['subtitle' => 0],
    'false' => ['subtitle' => false],
];

foreach ($cases as $name => $input) {
    printf("%s: exists=%s isset=%s\n", $name,
        array_key_exists('subtitle', $input) ? 'true' : 'false',
        isset($input['subtitle']) ? 'true' : 'false');
}

在PHP8.4.2執行php case.php,輸出如下。最值得留意的是第二行:鍵存在,但isset仍然為false。後三行則說明不能把空字串、0、false和null混成同一種狀態。

missing: exists=false isset=false
null: exists=true isset=false
empty: exists=true isset=true
zero: exists=true isset=true
false: exists=true isset=true

如果你的結果不同,先看測試資料是否真的保留null。有些表單流程會把空白欄位轉成null,也有資料處理層會先移除值為null的鍵。檢查的位置很重要:應在你要判斷的那個資料結構上測,而不是只看更早收到的原始字串。

部分更新時,先決定null代表什麼

以下例子約定:未提供subtitle就保留舊值;提供null就清空;提供字串就替換。這是示範用的資料規則,你的API也可以禁止null,但要先寫清楚,前端和後端才不會各自猜測。

$stored = ['subtitle' => '原副標題'];
$patch = ['subtitle' => null];

if (array_key_exists('subtitle', $patch)) {
    $value = $patch['subtitle'];
    if ($value !== null && !is_string($value)) {
        throw new InvalidArgumentException('副標題需為字串或null');
    }
    $stored['subtitle'] = $value;
}

這段程式先看鍵,所以不會漏掉清空指令;接著只接受字串或null,不讓布林false和數字0悄悄寫入。若你把第一個條件換成isset,null就無法走進更新區塊,原副標題會留在那裡。

這也不是要求所有isset都改成array_key_exists。當你只需要一個存在且非null的值,isset仍然合適。例如某個選填顯示文字為null時就應使用預設值,便不必保留「有傳null」這個差別。重要的是更新契約是否需要區分這兩種輸入。

問號問號與empty不能補回這個差別

$input['subtitle'] ?? '預設文字'很適合取得預設值,但不存在和null都會走到預設文字。若null表示清空,使用這個寫法反而可能把清空指令換成預設內容。先處理鍵存在的分支,再讀值,才保得住原本的意圖。

empty()檢查的範圍又更廣,空字串、0及false等值也可能被視為空。因此會員設定裡的「關閉」false、商品數量0、字串"0",都不宜直接用empty推論成「沒有提交」。如果欄位允許這些值,就應另做型別與範圍檢查,不要以方便的空值判斷取代資料規格。

巢狀資料要逐層確認

array_key_exists只檢查傳入陣列的第一層。資料放在$input['profile']['subtitle']時,不能對最外層查subtitle就認為它不存在。先確認profile是陣列,再檢查內層鍵,避免把null、字串或缺席的父層當成陣列使用。

if (isset($input['profile']) && is_array($input['profile'])
    && array_key_exists('subtitle', $input['profile'])) {
    $subtitle = $input['profile']['subtitle'];
}

這裡父層profile為null時會跳過,因為例子只允許profile是一個陣列。若你的規則另外允許profile=null代表清空整個設定,就應增加獨立分支,不能直接套用這段。array_key_exists的第二個參數也必須是陣列;物件屬性需要使用適合物件的檢查方式,不能照搬陣列用法。

表單欄位缺席,也可能是前端行為

假如你處理的是HTML表單,還要先確認欄位是否真的被提交。畫面看得到欄位,不代表請求裡一定包含它;例如前端程式可能在送出前重新整理欄位,或使用JSON序列化只挑選有改過的項目。收到的陣列沒有subtitle時,array_key_exists會正確回傳false,但原因需要回到提交流程找。

檢查時可以使用瀏覽器的網路面板,對照請求送出的欄位名稱與後端接收結果。只看頁面上的輸入框內容,無法證明後端收到哪個鍵。測試資料應用虛構副標題,不要把真實會員內容貼到錯誤畫面或公開討論區。若中間有驗證器、清理器或框架請求物件,也要看它們是否改寫了空字串與null。

將責任分清後,修法才會合適:根本沒有提交鍵,要修前端提交或欄位約定;鍵有提交而值為null,要修後端判斷;鍵和判斷都正確,但畫面沒有清空,則要往寫入及顯示流程查。不要在第一個false就加上預設值,讓問題暫時看起來消失。

用小函式保存更新約定

同一個欄位若在多個入口更新,可以把「缺席保留、null清空、字串替換」整理成一個小函式,再讓每個入口使用相同規則。函式應明確接收陣列與原值,不要依賴不清楚的全域請求;遇到不支援的型別,也應回報輸入問題,而不是先轉成字串。

這樣日後新增匯入工具或管理頁面時,較不容易發生一個入口能清空、另一個卻把null忽略的情況。測試也能直接呼叫函式,確認三種輸入與拒絕型別。只為這個單一欄位建立合適的規則即可,不必為所有空值設計一套龐大的通用轉換,避免把不同功能的null含義混在一起。

改完後,保留三種更新測試

修正後不要只測一個有文字的欄位。至少分別送出空陣列、subtitle=null、subtitle="新文字",確認保留、清空與替換各自符合約定。再加入空字串、0和false,檢查哪些值應被接受、哪些應回報輸入錯誤。

讀取資料與寫入資料也要分開核對:前端可能送了null,後端判斷正確,卻在寫入前又套上預設值。沿著同一個欄位看接收、驗證、更新到最後顯示的結果,比只在某一行換函式更容易找到真正卡住的位置。保留合成測試資料即可,不需要在正式用戶資料上試清空。

參考資料

原文鏈接:https://wntheme.com/php-isset-array-key-exists-null/,轉載請註明出處。
0

評論0

顯示驗證碼
沒有帳號?註冊  忘記密碼?