<textarea> 是用戶輸入多行文本的標準控件,但其換行符在不同系統、不同處理階段(HTML 顯示、數據庫存儲、JSON 傳輸、郵件發送)表現不一。本文系統梳理 \r\n/\n/\r 的來源、PHP 內建函數 nl2br()、手動分割處理、數據庫存儲最佳實踐、前端回填顯示、以及防 XSS 關鍵點。
| 系統/場景 | 換行符 | ASCII | PHP 表示 |
|---|---|---|---|
| Windows (CRLF) | \r\n | 13, 10 | "\r\n" 或 PHP_EOL (Windows 下) |
| Linux/Unix/macOS (LF) | \n | 10 | "\n" 或 PHP_EOL (Linux 下) |
| 舊 Mac OS 9 (CR) | \r | 13 | "\r" |
| HTML 顯示換行 | <br> / <br /> | — | nl2br() 輸出 |
| JSON 字符串轉義 | \n / \r\n | — | json_encode() 自動處理 |
關鍵點: 瀏覽器提交表單時,textarea 內容統一轉為 \n (LF) —— 無論用戶在 Windows 下輸入 \r\n,PHP $_POST['text'] 拿到的永遠是 \n 分割的字符串。
<?php
$text = "第一行\n第二行\r\n第三行\r第四行";
// 1. nl2br() —— 最常用:轉換為 HTML
echo nl2br($text);
/* 輸出:
第一行
第二行
第三行
第四行
*/
// 注意:nl2br() 只插入
,不移除原有 \n,源字符串不變。
// 2. explode() 分割成數組 —— 逐行處理/存儲
$lines = explode("\n", $text); // 或 preg_split('/\r\n|\r|\n/', $text) 兼容所有
print_r($lines);
/* Array ( [0]=>第一行 [1]=>第二行 [2]=>第三行 [3]=>第四行 ) */
// 3. 統一換行符為 \n (存儲標準化)
$normalized = preg_replace('/\r\n|\r/', "\n", $text);
// 4. 存儲前轉義 HTML (防 XSS) + 保留換行
$safeForDb = htmlspecialchars($normalized, ENT_QUOTES, 'UTF-8');
// 數據庫存儲的是:第一行\n第二行\n第三行\n第四行
// 5. 讀取後輸出到 HTML
echo nl2br($safeForDb); // 完美保留換行且安全
// 6. 回填到 textarea (編輯頁)
echo '<textarea>' . htmlspecialchars($normalized, ENT_QUOTES, 'UTF-8') . '</textarea>';
// textarea 內直接顯示換行,無需
<?php
// 接收
$raw = $_POST['content'] ?? '';
// 統一換行符 (防禦性,雖然瀏覽器已統一為 \n)
$normalized = preg_replace('/\r\n|\r/', "\n", $raw);
// XSS 防護:轉義 HTML 特殊字符,保留 \n
$safe = htmlspecialchars($normalized, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
// 長度限制
if (mb_strlen($safe) > 5000) {
exit('內容過長');
}
// 入庫 (使用預處理語句防 SQL 注入)
$stmt = $pdo->prepare('INSERT INTO articles (content) VALUES (?)');
$stmt->execute([$safe]);
<?php
$stmt = $pdo->query('SELECT content FROM articles WHERE id=1');
$row = $stmt->fetch();
$content = $row['content']; // 已經是 htmlspecialchars 過的,含 \n
// 列表頁:截取前 200 字符 + ...
$excerpt = mb_substr($content, 0, 200) . '...';
echo nl2br($excerpt); // 含
的 HTML 片段
// 詳情頁:完整輸出
echo '<div class="article-content">' . nl2br($content) . '</div>';
<textarea name="content" rows="10" cols="80"><?php
// 讀取數據庫原值 (已 htmlspecialchars),需反轉義才能正確顯示在 textarea
// 但我們入庫前已 htmlspecialchars,所以這裡必須 htmlspecialchars_decode
echo htmlspecialchars_decode($content, ENT_QUOTES);
?></textarea>
關鍵邏輯: 入庫前 htmlspecialchars() → 讀取後列表/詳情頁 nl2br() 直接輸出 → 編輯頁 htmlspecialchars_decode() 回填。永遠不要在入庫前用 nl2br(),否則數據庫存的是 HTML,導致 JSON API、郵件純文本、搜索索引全亂。
<?php
// 讀取數據庫值 (含 \n,已 htmlspecialchars)
$content = $row['content'];
// 選項 A:返回原始文本,前端自行處理換行 (推薦)
return response()->json(['content' => $content]);
// 前端:{content} 或 CSS white-space: pre-wrap
// 選項 B:後端轉
(不推薦,耦合前端渲染方式)
return response()->json(['content_html' => nl2br($content)]);
<?php
$mailBody = "親愛的用戶:\n\n" . htmlspecialchars_decode($content, ENT_QUOTES) . "\n\n—— 系統自動發送";
// 直接使用 \n,郵件客戶端原生支持
<?php
// CSV 單元格內換行需雙引號包裹 + 雙引號轉義
$csvCell = '"' . str_replace('"', '""', htmlspecialchars_decode($content, ENT_QUOTES)) . '"';
富文本編輯器提交的是 HTML (<p>...</p><br>),而非純文本。處理流程完全不同:
HTMLPurifier 過濾危險標籤/屬性,保留 p, br, strong, em, ul, ol, li, img 等白名單。nl2br() 或 htmlspecialchars()。| 錯誤做法 | 後果 | 正確做法 |
|---|---|---|
入庫前 nl2br() | 數據庫存 HTML,API/郵件/搜索全壞 | 入庫存純文本 (\n),輸出時 nl2br() |
只用 htmlspecialchars() 不統一換行符 | 極少數舊瀏覽器/爬蟲提交 \r 導致顯示異常 | preg_replace('/\r\n|\r/', "\n", $str) 統一 |
| 編輯頁直接輸出數庫值到 textarea | 顯示 <br /> 或實體字符 | htmlspecialchars_decode() 後再輸出 |
用 str_replace("\n", "<br>", $str) | 漏掉 \r\n、\r,且未轉義 HTML | 用 nl2br(htmlspecialchars(...)) |
前端 white-space: pre-wrap 但後端已 nl2br() | 雙重換行,空行過大 | 二選一:後端 nl2br + 正常 div,或後端純文本 + CSS pre-wrap |
<?php
namespace App\Utils;
class Textarea {
// 入庫前處理
public static function forStorage(string $raw): string {
$normalized = preg_replace('/\r\n|\r/', "\n", $raw);
return htmlspecialchars($normalized, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
// 列表/詳情頁輸出 HTML
public static function toHtml(string $stored): string {
return nl2br($stored);
}
// 編輯頁回填
public static function forEdit(string $stored): string {
return htmlspecialchars_decode($stored, ENT_QUOTES);
}
// API 純文本 (前端 pre-wrap)
public static function forApi(string $stored): string {
return htmlspecialchars_decode($stored, ENT_QUOTES);
}
// 純文本郵件/導出
public static function forPlainText(string $stored): string {
return htmlspecialchars_decode($stored, ENT_QUOTES);
}
}
// 使用
$safe = Textarea::forStorage($_POST['content']);
$html = Textarea::toHtml($row['content']);
$edit = Textarea::forEdit($row['content']);
核心原則: 數據庫只存「純文本 + 統一 \n + htmlspecialchars 轉義」。讀取時根據場景:HTML 頁面用 nl2br()、編輯頁用 htmlspecialchars_decode()、API/郵件用純文本。這套流程同時解決了換行保留、XSS 防護、多端適配三大問題。切記:nl2br() 是輸出時的視圖層操作,而非存儲層操作。