經典錯誤 Warning: Cannot modify header information - headers already sent by... 困擾無數 PHP 開發者。本文從 HTTP 協議層面剖析成因,提供三大徹底解決方案與最佳實踐。
<?php
session_start();
$_SESSION['username'] = $username;
// 輸出 HTML/JS 導致 Header 已送出
echo "<script>location.href='../admin.php';</script>";
exit;
?>
任何在 header()、session_start()、setcookie() 之前的輸出(包括空白字元、BOM、HTML、echo/print、錯誤訊息)都會導致 HTTP Header 提前送出,後續 Header 操作即失敗。
php.ini(全域生效)output_buffering = 4096 ; 或 On(無限制緩衝),單位位元組
原理:PHP 將輸出先存入內部緩衝區(預設 4KB),直到腳本結束或緩衝區滿才發送給瀏覽器,此期間可隨意呼叫 Header 相關函式。
ob_start()(局部控制)<?php
ob_start(); // 開啟輸出緩衝,必須在任何輸出之前
session_start();
$_SESSION['username'] = $username;
echo "<script>location.href='../admin.php';</script>";
// 腳本結束自動 flush,或手動 ob_end_flush()
?>
進階:ob_start('ob_gzhandler') 可同時啟用 GZIP 壓縮輸出。
現象:檔案以 UTF-8 with BOM 編碼儲存,開頭三個位元組 EF BB BF 會被 PHP 直接輸出,導致 Header 提前送出。
檢測:hexdump -C file.php | head -1 或編輯器(VS Code、Notepad++、PhpStorm)右下角編碼顯示。
解決:全專案統一使用「UTF-8 無 BOM」編碼儲存。現代編輯器預設即為無 BOM,僅舊版 Windows 記事本會強制加入 BOM。
影響:BOM 亦會導致 session_start() 失效、Cookie 無法送出、XML/JSON 輸出解析錯誤。
header()/session_start()。output_buffering 開啟或呼叫 ob_start())。echo、print、HTML 區塊輸出寫入緩衝區,不發送給 TCP 層。header()、setcookie()、session_start() 將 Header 存入內部 Header 列表,不立即發送。ob_end_flush(),PHP 依序發送:Header 列表 → 空行 → 緩衝區內容。| 誤區 | 正確認知 |
|---|---|
set_time_limit() 影響 Header 發送 | 無關,set_time_limit 控制腳本最大執行秒數 |
在 include/require 檔案中輸出不算 | 算!被包含檔案的輸出同樣會觸發 Header 送出 |
只有 echo 才算輸出 | print、printf、var_dump、HTML 區塊、錯誤訊息、BOM 皆算 |
用 ob_start() 就能無限制輸出 | 記憶體耗盡仍會崩潰,大量輸出應改用串流 fwrite(STDOUT, ...) 或分塊 flush |
<?php
// 放在專案入口檔案最頂端,檢測是否已有輸出
if (headers_sent($file, $line)) {
error_log("Headers already sent in $file on line $line");
// 開發環境可直接 die 顯示
// die("Headers sent at $file:$line");
}
?>
掌握 HTTP Header 輸出機制、啟用 Output Buffering、杜絕 BOM、採用分層架構,即可徹底根治此經典錯誤。