必须先 htmlspecialchars 再 preg_replace,否则 xss;关键词需 preg_quote 转义并按长度降序处理;中文空格需归一化;富文本须用 domdocument 处理 text_node 节点。

先 htmlspecialchars 再 preg_replace,顺序不能反
用户输入的关键词可能含 <script></script>,原文可能含未转义的 HTML。如果先高亮再转义,<mark></mark> 会被当作文本输出,而原始脚本仍保留在匹配结果里——直接触发 XSS。正确顺序只有一种:htmlspecialchars($text, ENT_QUOTES, 'UTF-8') 得到安全字符串,再对这个结果调用 preg_replace。
常见错误现象:
– 搜索 <script>alert(1)</script>,页面弹窗
– 搜索 php,把 <span class="php-item"></span> 里的 php 也高亮了
– 中文显示成一堆 中文(用了 htmlentities)
- 绝对不要用
htmlentities:它会把所有中文转成数字实体,影响可读性 - 若原文已含合法 HTML(如富文本),不能全量
htmlspecialchars——得用DOMDocument解析,只对TEXT_NODE节点内容高亮 - 高亮后的内容含
<mark></mark>,输出时无需再htmlspecialchars,但其他用户输入字段仍需独立过滤
关键词必须用 preg_quote 转义,且按长度降序处理
用户搜 a+b 或 file[config],不转义就会被当成正则元字符,轻则匹配失败,重则报 PREG_NO_ERROR 错误。同时,多个词共存时(如 php 和 phpstorm),必须先处理长词,否则短词提前替换会污染原文,导致长词无法命中。
- 调用
preg_quote($keyword, '/'),第二个参数必须是正则分隔符(如/) - 多关键词先排序:
usort($keywords, function($a, $b) { return strlen($b) - strlen($a); }); - 每个词单独
preg_replace,不要拼成一个正则——调试困难、边界冲突、性能无优势 - 模式加
'iu'修饰符:'/'.preg_quote($kw, '/').'/iu',u支持 UTF-8 中文
中文空格和全角符号必须归一化
PHP 的 \s 不识别全角空格(\xe3\x80\x80)、不间断空格(\xc2\xa0)或换行符。用户输“人工智能 开源”,原文用的是全角空格,直接匹配必然失败。
- 预处理关键词:
$keyword = str_replace([' ', "\r", "\n", "\t", "\xc2\xa0", "\xe3\x80\x80"], ' ', $keyword); - 再压缩连续空格:
$keyword = preg_replace('/\s+/', ' ', $keyword); - 原文也要同样处理,否则前后不一致
- 中文无天然词边界(
\b对汉字无效),强行加\b反而漏匹配;需要“整词”效果时,用(? 和 <code>(?!\w)替代,但多数场景去掉更稳
富文本内容要跳过 HTML 标签处理
直接对含标签的字符串跑 preg_replace,可能把 <img alt="php icon"> 里的 php 高亮,也可能把 class="highlight" 里的 light 错误包裹。这不是正则能干净解决的问题。
- 用
DOMDocument加载 HTML,遍历所有TEXT_NODE节点 - 对每个节点的
nodeValue先htmlspecialchars,再高亮,最后赋回nodeValue - 跳过
SCRIPT、STYLE、TEXTAREA等节点(它们的内容不该参与搜索展示) - 别依赖正则“排除标签内”的写法(如
(?)(...)(?=),边界复杂、不可靠,DOM 是唯一稳健路径
真正难的不是写对一行 preg_replace,而是判断原文是否含 HTML、决定要不要 DOM 解析、以及在 XSS 防御和功能表现之间做取舍——这些点一旦忽略,上线后要么被黑,要么用户搜不到想要的结果。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











