內容管理系統常見需求:文章發布時自動將預設關鍵字轉為超連結(如關鍵字「SEO」連結至 `/seo-guide`)。本文提供一個健壯的 keylink() 函數,解決關鍵字包含關係(如「PHP」與「PHP 7」)、替換次數控制、HTML 標籤保護等實務難題。
<?php
/**
* 文章關鍵字自動加連結
*
* @param string $str 原始文章內容
* @param array $linkDefs 關鍵字定義陣列,格式:['關鍵字,網址', ...]
* @param int $count 每個關鍵字最大替換次數(預設 1,0 表不限制)
* @return string 處理後的 HTML 內容
*/
function keylink(string $str, array $linkDefs, int $count = 1): string
{
// 1. 解析定義陣列為 [關鍵字 => 網址]
$linkMap = [];
foreach ($linkDefs as $row) {
$parts = explode(',', $row, 2);
if (count($parts) === 2) {
$linkMap[trim($parts[0])] = trim($parts[1]);
}
}
// 2. 先移除已存在的 標籤內同關鍵字,避免巢狀連結
foreach ($linkMap as $kwd => $url) {
$pattern = '/(<a.*?>\s*)' . preg_quote($kwd, '/') . '(\s*<\/a>)/sui';
$str = preg_replace($pattern, '${2}', $str);
}
// 3. 依關鍵字長度由長到短排序,優先處理長關鍵字(如「PHP 7」先於「PHP」)
uksort($linkMap, function ($a, $b) {
return strlen($b) - strlen($a);
});
// 4. 處理關鍵字包含關係:暫時替換子關鍵字為唯一佔位符
$placeholders = [];
$keywords = array_keys($linkMap);
foreach ($keywords as $i => $kwd) {
for ($j = $i + 1; $j < count($keywords); $j++) {
$subKwd = $keywords[$j];
if (strpos($kwd, $subKwd) !== false) {
$ph = '{' . md5($subKwd) . '}';
$kwd = str_replace($subKwd, $ph, $kwd);
$placeholders[$ph] = $subKwd;
}
}
// 5. 實際替換為超連結
$str = preg_replace(
'/' . preg_quote($kwd, '/') . '/sui',
'<a href="' . htmlspecialchars($linkMap[$kwd], ENT_QUOTES) . '" target="_blank">' . $kwd . '</a>',
$str,
$count
);
}
// 6. 還原佔位符
foreach ($placeholders as $ph => $orig) {
$str = str_replace($ph, $orig, $str);
}
return $str;
}
// 使用範例
$links = [
'百度,https://www.baidu.com/',
'伺服器軟體,https://s.jb51.net/',
'PHP,https://www.php.net/',
'PHP 7,https://www.php.net/releases/7_0_0.php',
];
$article = '在百度搜尋 PHP 7 教學,也可查詢伺服器軟體相關資訊。';
echo keylink($article, $links, 1);
// 輸出:在<a href="https://www.baidu.com/" target="_blank">百度</a>搜尋 <a href="https://www.php.net/releases/7_0_0.php" target="_blank">PHP 7</a> 教學,也可查詢<a href="https://s.jb51.net/" target="_blank">伺服器軟體</a>相關資訊。
?>
uksort 依關鍵字長度遞減,確保「PHP 7」優先於「PHP」被替換,避免變成 `PHP 7`。$count 參數直接傳給 preg_replace,預設只替換第一次出現,避免關鍵字堆砌影響閱讀體驗。htmlspecialchars 處理,防止 XSS。preg_replace_callback_array 單次遍歷,或建立 Trie 樹/AC 自動機進行多模式匹配(可參考 php-aho-corasick 擴展)。mb_strpos 定位後局部替換,避免單次正則處理超大字串導致記憶體飆升。$linkMap 序列化快取,避免每次請求重複解析排序。| 問題 | 原因 | 修正 |
|---|---|---|
| 關鍵字包含 HTML 標籤字元(如 `<` `>`) | 正則未轉義 | 使用 preg_quote($kwd, '/') 轉義特殊字元 |
| 中文關鍵字匹配失效 | 未加 u 修飾符 | 正則加 /sui:s=單行模式、u=UTF-8、i=不區分大小寫 |
| 替換後連結指向錯誤 | 佔位符還原順序錯誤 | 確保長關鍵字先處理,佔位符用 MD5 確保唯一 |