strpos(string $haystack, string $needle, int $offset = 0): int|false 返回 $needle 在 $haystack 中首次出現的位置(從 0 開始),未找到返回 false。區分大小寫,不區分大小寫請用 stripos()。
<?php
/**
* 關鍵詞過濾器(strpos 版)
* 支持從文件加載,自動處理 BOM、換行符、空白字符
*/
class KeywordFilter {
private $keywords = [];
/**
* @param string $filePath 關鍵詞文件路徑,每行一個詞
* @param bool $caseInsensitive 是否不區分大小寫
*/
public function __construct(string $filePath, bool $caseInsensitive = false) {
$this->loadFromFile($filePath);
$this->caseInsensitive = $caseInsensitive;
}
private function loadFromFile(string $path) {
if (!is_file($path) || !is_readable($path)) {
throw new InvalidArgumentException("關鍵詞文件不可讀:$path");
}
$content = file_get_contents($path);
// 移除 BOM
$content = preg_replace('/^\xEF\xBB\xBF/', '', $content);
// 統一換行符
$content = str_replace(["\r\n", "\r"], "\n", $content);
// 按行分割,過濾空行並 trim
$this->keywords = array_filter(array_map('trim', explode("\n", $content)), function($w) {
return $w !== '';
});
}
/**
* 檢測文本是否包含關鍵詞
* @param string $text
* @return string|false 匹配到的關鍵詞,未命中返回 false
*/
public function check(string $text) {
$func = $this->caseInsensitive ? 'stripos' : 'strpos';
foreach ($this->keywords as $kw) {
// 關鍵:每次比對前 trim,防止文件行尾空格導致誤判
$kw = trim($kw);
if ($kw === '') continue;
if ($func($text, $kw) !== false) {
return $kw; // 返回首個命中的詞
}
}
return false;
}
/**
* 替換所有關鍵詞為指定字符
* @param string $text
* @param string $replacement
* @return string
*/
public function replace(string $text, string $replacement = '***') {
$func = $this->caseInsensitive ? 'stripos' : 'strpos';
foreach ($this->keywords as $kw) {
$kw = trim($kw);
if ($kw === '') continue;
// 使用 preg_replace 實現不區分大小寫替換
if ($this->caseInsensitive) {
$text = preg_replace('/' . preg_quote($kw, '/') . '/i', $replacement, $text);
} else {
$text = str_replace($kw, $replacement, $text);
}
}
return $text;
}
}
// === 使用示例 ===
// keywords.txt 內容(UTF-8 無 BOM):
// 關鍵字1
// 關鍵字2
// 敏感詞
$filter = new KeywordFilter(__DIR__ . '/keywords.txt', true); // 不區分大小寫
$content = '這裡是要發布的內容,含有關鍵字2';
$hit = $filter->check($content);
if ($hit) {
echo "檢測到關鍵詞:$hit\n";
echo "凈化後:" . $filter->replace($content) . "\n";
} else {
echo "通過關鍵詞檢測\n";
}
print_r($keywords) 顯示數組正常,但 var_dump($keywords[0]) 發現字符串長度比可見字符多explode("\n") 後每行尾部殘留 \r 或 BOM 字符trim() 每行關鍵詞,或統一轉換文件編碼為 **UTF-8 無 BOM**、**LF 換行**| 方法 | 時間複雜度 | 適用場景 |
|---|---|---|
| strpos 遍歷 | O(n × m) | 關鍵詞 < 500,文本 < 1KB |
單正則合並 /(kw1|kw2|kw3)/ | O(n) 正則引擎優化 | 關鍵詞 < 2000,需替換操作 |
| Aho-Corasick 自動機 (DFA) | O(n + m + z) | 關鍵詞 > 5000,高頻檢測,最長匹配 |
// 關鍵詞文件格式擴展:每行「類型:模式」
// 普通:敏感詞
// 正則:\d{11} (匹配手機號)
// 通配:傻*瓜 (內部轉換為 正則 傻.*?瓜)
public function loadFromFile(string $path) {
// ... 讀取文件 ...
foreach ($lines as $line) {
$line = trim($line);
if (!$line || $line[0] === '#') continue; // 支持註釋
if (strpos($line, ':') !== false) {
list($type, $pattern) = explode(':', $line, 2);
$this->keywords[$type][] = $pattern;
} else {
$this->keywords['plain'][] = $line;
}
}
}
通過 trim() 消除隱形字符干擾,結合 stripos 實現不區分大小寫檢測,是中小型項目關鍵詞過濾的輕量級首選。