BOM(Byte Order Mark,位元組順序標記)是 UTF-8 檔案開頭的三個隱形字元 EF BB BF(十六進制),用於標識編碼。但在 PHP 中會導致:
header()、session_start() 提前輸出錯誤)<?php
function removeBOM(string $str): string {
// UTF-8 BOM = chr(239).chr(187).chr(191) = pack('CCC', 0xEF, 0xBB, 0xBF)
$bom = "\xEF\xBB\xBF";
return str_starts_with($str, $bom) ? substr($str, 3) : $str;
}
// 使用範例
$content = file_get_contents('config.php');
$clean = removeBOM($content);
file_put_contents('config.php', $clean);
?>
<?php
function clearFileBOM(string $filepath): bool {
if (!is_file($filepath)) return false;
$content = file_get_contents($filepath);
$clean = removeBOM($content);
if ($clean !== $content) {
return file_put_contents($filepath, $clean, LOCK_EX) !== false;
}
return true; // 原本就沒有 BOM
}
?>
<?php
/**
* 遞迴清理目錄下所有 PHP/HTML/JS/CSS 檔案的 BOM
* @param string $root 根目錄
* @param array $exts 要處理的副檔名
* @return array ['cleaned' => [], 'errors' => []]
*/
function cleanDirectoryBOM(string $root, array $exts = ['php', 'html', 'htm', 'js', 'css', 'xml', 'json']): array {
$result = ['cleaned' => [], 'errors' => []];
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($root, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($iterator as $file) {
if (!$file->isFile()) continue;
$ext = strtolower($file->getExtension());
if (!in_array($ext, $exts, true)) continue;
$path = $file->getPathname();
try {
if (clearFileBOM($path)) {
$result['cleaned'][] = $path;
}
} catch (Throwable $e) {
$result['errors'][$path] = $e->getMessage();
}
}
return $result;
}
// 使用範例
$report = cleanDirectoryBOM(__DIR__ . '/src');
echo "清理完成:" . count($report['cleaned']) . " 個檔案\n";
if ($report['errors']) {
print_r($report['errors']);
}
?>
將以下程式碼存為 bom_cleaner.php 放入專案根目錄,透過瀏覽器訪問即可掃描並清理:
<?php
// bom_cleaner.php
$BASE_DIR = __DIR__;
$AUTO_REMOVE = true; // true=自動移除, false=僅檢測
header('Content-Type: text/html; charset=utf-8');
?>
<!DOCTYPE html>
<html><head><title>UTF-8 BOM 清理工具</title>
<style>body{font-family:sans-serif;max-width:900px;margin:2rem auto;padding:0 1rem}
.ok{color:green}.warn{color:orange}.err{color:red} pre{background:#f5f5f5;padding:1rem;overflow:auto}</style></head><body>
<h1>UTF-8 BOM 掃描與清理</h1>
<p>目錄:isFile()) continue;
$ext = strtolower($file->getExtension());
if (!in_array($ext, ['php','html','htm','js','css','xml','json','txt','sql'], true)) continue;
$path = $file->getPathname();
if (hasBOM($path)) {
$found++;
$rel = str_replace($BASE_DIR . DIRECTORY_SEPARATOR, '', $path);
if ($AUTO_REMOVE) {
$ok = removeBOMFile($path);
echo "" . ($ok ? '✅ 已清理' : '❌ 失敗') . ": $rel
";
} else {
echo "⚠️ 發現 BOM: $rel
";
}
}
}
echo "
總計發現 $found 個含 BOM 檔案
";
?>
</body></html>
# 檢測含 BOM 的檔案
grep -r -l $'^\xEF\xBB\xBF' /path/to/project --include="*.php" --include="*.html"
# 批次移除 BOM(備份原檔)
find /path/to/project -type f \( -name "*.php" -o -name "*.html" \) -exec sed -i.bak '1s/^\xEF\xBB\xBF//' {} +
# 僅移除不備份
find /path/to/project -type f -name "*.php" -exec sed -i '1s/^\xEF\xBB\xBF//' {} +
| 編輯器 | 設定路徑 | 關鍵選項 |
|---|---|---|
| VS Code | Settings → Files: Encoding | 勾選「自動猜測編碼」,儲存時選「UTF-8 (without BOM)」 |
| PhpStorm | File → File Encoding | 專案預設 UTF-8,移除「Add BOM」勾選 |
| Sublime Text | File → Save with Encoding | 選「UTF-8」而非「UTF-8 with BOM」 |
| Notepad++ | 編碼 → 選「UTF-8」 | 避免選「UTF-8-BOM」 |
# .git/hooks/pre-commit
#!/bin/bash
# 暫存區檢查 BOM
if git diff --cached --name-only | xargs grep -l $'^\xEF\xBB\xBF' 2>/dev/null; then
echo "❌ 暫存區包含 BOM 的檔案,請先移除"
exit 1
fi
exit 0
BOM 是 UTF-8 歷史遺留產物,現代工具鏈均不推薦使用。建立「編輯器無 BOM 設定 + CI/CD 檢查 + 專案清理腳本」三層防線,才能徹底根除 BOM 導致的隱性 Bug。