「這段清理非法字元的 regex 看起來很複雜,是自己想出來的嗎?」
今天要看的這段程式碼,作者的答案很誠實:不是。方法上方直接留了一個外部連結,標明這段解法來自一篇公開文章。這篇不是要講「這段 regex 怎麼寫出來的」,而是想談一個更值得記住的態度:不是每個防禦手法都要自己發明,找到權威來源直接用、但要真的懂它在防什麼,這才是負責任的做法。
這個系統要處理外部系統透過 SOAP 回傳的 XML 資料,這些資料裡有時候會帶有不合法的 UTF-8 位元組序列、或者控制字元。如果直接把這些髒資料塞進資料庫、或者拿去做進一步的 XML/JSON 處理,輕則資料顯示亂碼,重則整個解析流程直接拋出例外中斷。
系統裡有一個工具方法,專門處理這個問題:
public static function stripInvalidXmlString(string $string): string
{
$string = preg_replace_callback(
'/&#x(\w+);/',
static fn ($m) => mb_convert_encoding($m[0], 'UTF-8', 'HTML-ENTITIES'),
$string
);
return self::sanitizeXML($string);
}
裡面呼叫的 sanitizeXML() 私有方法,內容是一段密度很高的 regex 處理邏輯——逐段檢查並移除不合法的 UTF-8 位元組樣式,再逐字元檢查是不是落在 XML 規範允許的合法字元範圍內,不合法的直接濾掉。
這段方法上方的註解,直接寫了一個外部連結,標明這是抄一篇公開文章的解法,不是團隊自己研究出來的成果。這是這篇想強調的核心態度:面對「清理不合法 UTF-8/XML 字元」這種已經有大量前人研究過、有標準答案可以參考的問題,不需要每個團隊都重新發明一次輪子。找到一個講得清楚、有權威來源的解法,直接引用,比自己憑空重新設計一套(很可能還漏掉某些邊角情況)更可靠。
但「抄」跟「照抄卻完全不懂」是兩回事。這段程式碼即使是引用來的,使用它的人也應該理解:它在防的是「不合法的 UTF-8 位元組」跟「XML 規範不允許的控制字元」這兩類問題,理解了這一層,才知道這段防禦函式的適用範圍在哪——它處理的是字元編碼層級的髒資料,不是處理「資料內容邏輯上合不合理」這種業務層級的驗證。如果誤以為這段函式順便做了業務驗證,之後遇到業務層級的髒資料反而漏掉檢查,就是「抄了但沒真的懂」的後果。
處理外部系統同步資料的 Job,在寫入站內資料表前,會呼叫這個工具方法清理過一次欄位內容,才把資料寫進資料庫。這不是憑空舉的例子——外部系統回傳的資料,格式跟編碼確實不是完全可控的,這段防禦邏輯是真的會在生產環境被觸發、真的會被用到的程式碼,不是寫好放著沒人用的裝飾。
❌ 自己憑感覺寫一段簡化版的清理邏輯
// 自己土法煉鋼寫一個簡單的過濾,可能漏掉某些邊角的不合法位元組樣式
$string = preg_replace('/[^\x20-\x7E]/', '', $string);
// 這種寫法會把所有非 ASCII 字元都濾掉,中文內容也會被誤殺
✅ 引用有來源的完整解法,並理解它在防什麼
/**
* @link https://example.com/php-skip-invalid-characters-xml-string/
*/
private static function sanitizeXML($string)
{
// 完整處理 UTF-8 不合法位元組樣式 + XML 規範合法字元範圍
// 理解這段在防「編碼層級」的問題,不是「業務層級」的驗證
}
你的專案裡有沒有一段程式碼是「抄」來的解法?回想一下,你是真的理解它在防什麼、適用範圍在哪,還是只是「用起來沒出過問題所以繼續用」?如果現在要你跟同事解釋這段程式碼在做什麼,你講得清楚嗎?
明天要看另一個跟外部系統對接的技巧組合:一支只有 38 行的 Crawler,怎麼用 Generator 分頁、搭配 PSR-18 discovery 跟 retry plugin,做出一支技巧密度很高的爬取邏輯。