Assuming your blacklist of bad words to be masked are fully comprised of letters or at least of word characters (allowing for digits and underscores), you won't need to call preg_quote()
before imploding and inserting into the regex pattern.
Use the \G
metacharacter to continue matching after the first letter of a qualifying word is matched. Every subsequently matched letter in the bad word will be replaced 1-for-1 with an asterisk.
\K
is used to forget/release the first letter of the bad word.
This approach removes the need to call preg_replace_callback()
to measure every matched string and write N asterisks after the first letter of every matches bad word in a block of text.
Breakdown:
/ #start of pattern delimiter
(?: #non-capturing group to encapsulate logic
\b #position separating word character and non-word character
(?= #start lookahead -- to match without consuming letters
(?:fook|shoot) #OR-delimited bad words
\b #position separating word character and non-word character
) #end lookahead
\w #first word character of bad word
\K #forget first matched word character
| #OR -- to set up \G technique
\G(?!^) #continue matching from previous match but not from the start of the string
) #end of non-capturing group
\w #match non-first letter of bad word
/ #ending pattern delimiter
i #make pattern case-insensitive
Code: (Demo)
$bad = ['fook', 'shoot'];
$pattern = '/(?:\b(?=(?:' . implode('|', $bad) . ')\b)\w\K|\G(?!^))\w/i';
echo preg_replace($pattern, '*', 'Holy fook n shoot, Batman; The Joker\'s shooting The Riddler!');
// Holy f*** n s****, Batman; The Joker's shooting The Riddler!