每個 PHP 開發者都曾遇到過這個經典警告:
Warning: Invalid argument supplied for foreach() in /path/to/file.php on line 95
這意味著 foreach 期望接收一個陣列或實作 Traversable 介面的物件,實際卻收到了 null、false、純量值或未定義變數。
| 傳入值類型 | 常見來源 | 錯誤示例 |
|---|---|---|
null | 資料庫查無資料、API 回傳失敗、變數未初始化 | $data = $db->query(...); foreach($data as $v) |
false | 函數執行失敗返回、json_decode 失敗 | $arr = json_decode($invalidJson); foreach($arr as $v) |
| 純量值 | 字串、整數、浮點數 | $str = 'hello'; foreach($str as $v) |
| 未定義變數 | 拼寫錯誤、作用域問題 | foreach($itmes as $v) (應為 $items) |
<?php
// 同時檢查是陣列且非空
if (is_array($items) && !empty($items)) {
foreach ($items as $item) {
// 處理邏輯
}
}
?>
優點:語義清晰、效能最佳、避免不必要的迭代
缺點:程式碼較冗長
<?php
// 將任意值轉為陣列,非陣列變單元素陣列,null 變空陣列
foreach ((array)$items as $item) {
// 處理邏輯
}
?>
優點:一行解決、相容性強
缺點:掩蓋了上游資料異常、false 會變成 [false] 而非 [],可能引入邏輯錯誤
<?php
// PHP 8.0+ 可用 ?-> 搭配 ?? 空陣列
foreach (($items?->toArray() ?? []) as $item) {
// 處理邏輯
}
?>
<?php
/**
* 安全迭代器:保證回傳可迭代物件
* @param mixed $data
* @return \Traversable|array
*/
function safe_iterable($data): iterable
{
if (is_array($data)) return $data;
if ($data instanceof \Traversable) return $data;
if (is_null($data) || $data === false) return [];
// 純量值包裝為單元素陣列(視業務決定是否需要)
return [$data];
}
// 使用
foreach (safe_iterable($items) as $item) {
// 絕不拋出警告
}
?>
治本之道是在資料進入迴圈前就確保型別正確:
<?php
// 資料庫查詢範例
$stmt = $pdo->query('SELECT * FROM users WHERE status = 1');
$users = $stmt->fetchAll(PDO::FETCH_ASSOC) ?? []; // 確保是陣列
// API 回傳處理
$response = $httpClient->get('/api/users');
$users = json_decode($response->getBody(), true);
if (!is_array($users)) {
$users = []; // 或拋出異常、記錄日誌
}
// 模板渲染前賦值
$view->assign('users', $users ?? []);
?>
遇到警告時,在 foreach 前加入臨時除錯代碼:
<?php
// 臨時除錯
var_dump($items, gettype($items), get_debug_type($items)); // PHP 8+ get_debug_type 更精確
// 或寫入日誌
error_log('foreach input: ' . print_r($items, true));
foreach ($items as $item) { ... }
?>
?->、??、get_debug_type()記住:警告不是錯誤,但往往預示著潛在 Bug。花幾秒鐘加上防禦性檢查,能省下數小時的線上排查時間。