常規 str_replace 或 preg_replace 執行的是「全局替換」:目標字符串中所有匹配項都會被替換為同一值。但在某些業務場景(如模板引擎佔位符替換、敏感詞分級處理、文章段落定向修改)中,我們需要第一次匹配替換為值 A,第二次匹配替換為值 B,第三次替換為值 C...,即「一對一映射替換」。
更棘手的是:若替換值本身包含搜索關鍵詞,會觸發「鏈式替換」導致結果錯亂。
preg_match_all 獲取所有匹配項的索引順序。$tmp_match),先將所有目標關鍵詞替換為佔位符,再按映射表將佔位符替換為最終值。preg_replace 的 $limit = 1 參數,確保每次僅替換一個佔位符。multiple_replace_words<?php
/**
* 數組關鍵詞一對一精確替換
*
* @param string $word 搜索關鍵詞(支持正則模式)
* @param array $replace 替換值數組,按順序對應每次匹配
* @param string $string 目標字符串
* @param string $tmp_match 臨時佔位符,需確保不在原字符串中出現
* @return string|false 替換後字符串,無匹配返回 false
*/
function multiple_replace_words($word, $replace, $string, $tmp_match = '#__TMP_REPLACE_' . uniqid() . '__#') {
// 1. 匹配所有關鍵詞位置
if (!preg_match_all('/' . $word . '/', $string, $matches)) {
return false; // 無匹配
}
$matched = $matches[0];
$count = count($matched);
// 2. 構建最終替換值數組:有值用替換值,無值保持原匹配內容
$finalReplacements = array();
for ($i = 0; $i < $count; $i++) {
$finalReplacements[] = isset($replace[$i]) ? $replace[$i] : $matched[$i];
}
// 3. 防止替換循環:先將所有匹配項統一替換為唯一佔位符
// 構建搜索模式數組:每個匹配項對應一個獨立模式(保持順序)
$searchPatterns = array();
foreach ($matched as $m) {
$searchPatterns[] = '/' . preg_quote($m, '/') . '/';
}
// 先全部替換為佔位符
$string = preg_replace($searchPatterns, $tmp_match, $string);
// 4. 逐個將佔位符替換為最終值 (limit=1 確保每次只替換第一個佔位符)
foreach ($finalReplacements as $replacement) {
$string = preg_replace('/' . preg_quote($tmp_match, '/') . '/', $replacement, $string, 1);
}
return $string;
}
$str = 'aaabaaacaaadaaa'; // 匹配 'aaa' 出現 4 次
$word = 'aaa';
$replace = array(null, 'XXX', 'YYY'); // 第1次不替換(null保持原樣), 第2次替換XXX, 第3次YYY, 第4次保持原樣
echo multiple_replace_words($word, $replace, $str);
// 輸出: aaabXXXcYYYdaaa
// 解析:
// 原文: aaa b aaa c aaa d aaa
// 索引: 0 1 2 3
// 替換: 保留 XXX YYY 保留
$str = '中文aaab中文ccaaad中文eee';
$word = '中文';
$replace = array(null, '(替換2)', '(替換3)');
echo multiple_replace_words($word, $replace, $str);
// 輸出: 中文aaab(替換2)ccaaad(替換3)eee
$str = 'A_B_C';
$word = '_'; // 匹配下劃線
$replace = array('X', 'Y'); // 想替換前兩個下劃線
// 普通 str_replace('_', 'X', $str) -> 'AXBXC' (全部替換)
// 普通 str_replace('_', ['X','Y'], $str) -> 報錯/行為未定義
// 本函數:
echo multiple_replace_words('_', ['X', 'Y'], $str);
// 輸出: 'AXBYC' (精確一對一)
/* 鏈式替換測試:替換值包含搜索關鍵詞 */
$str = 'apple';
$word = 'p';
$replace = array('pp', 'le'); // 第一個替換值 'pp' 包含 'p'
// 如果不防護:第一次替換 -> 'apple' -> 'appple' -> 第二次匹配新產生的 'p' -> 混亂
// 本函數輸出:
echo multiple_replace_words($word, $replace, $str);
// 輸出: 'apple' -> 第一個p變pp -> 'apple' (佔位符機制隔離了干擾)
uniqid() 或 md5(microtime(true)) 生成極低衝突概率的佔位符,避免與原文衝突。$word 若包含正則元字符(如 . * ?),需調用 preg_quote($word, '/') 轉義,或要求調用者傳入已轉義模式。preg_replace 可能較慢。可考慮 preg_replace_callback 配合計數器單次遍歷完成替換。preg_ 系列函數在 UTF-8 下需注意 /u 修飾符。這個函數解決了 PHP 內置函數無法直接實現的「有序、一對一、防鏈式」替換需求。核心思想是「佔位符隔離 + 限次替換」,代碼簡潔且邏輯閉環。適用於模板渲染、內容審核分級、數據脫敏等精確控制替換順序的場景。