preg_replace
What Is preg_replace() ? In simple terms: preg_replace() finds text that matches a regular expression and replaces it with something else. Unlike str_replace() , which replaces fixed text, it can repl
What Is preg_replace()?
In simple terms: preg_replace() finds text that matches a regular expression and replaces it with something else. Unlike str_replace(), which replaces fixed text, it can replace patterns — any number, any run of spaces, any date in a certain format — and it can reuse parts of the match in the replacement.
Technically: preg_replace() searches the subject for all matches of the pattern and replaces each with the replacement string, in which $n or ${n} refer to captured groups. It returns the new string (or array), or null if an error occurs. The original string is not modified.
Syntax
preg_replace(
string|array $pattern,
string|array $replacement,
string|array $subject,
int $limit = -1,
int &$count = null
): string|array|null
| Parameter | Meaning |
|---|---|
$pattern | Pattern(s) to search for |
$replacement | Replacement text, with $1, ${1}, … for groups |
$subject | String or array of strings to process |
$limit | Maximum replacements per pattern and subject (-1 \= unlimited) |
$count | Filled with the number of replacements made |
Basic Replacement
Example
<?php
$text = "Too many spaces \t and\ttabs";
echo preg_replace('/\s+/', ' ', $text) . "\n";
echo preg_replace('/\d/', '#', 'Card 4111 1111') . "\n";
Output
Too many spaces and tabs
Card #### ####
\s+matches any run of whitespace characters; each run becomes a single space.\dmatches a single digit; every digit becomes#.
Using Captured Groups in the Replacement
Groups captured in the pattern can be inserted into the replacement with $1, $2, … ($0 is the whole match).
Reformatting a Date
<?php
echo preg_replace('/(\d{4})-(\d{2})-(\d{2})/', '$3/$2/$1', 'Due on 2026-10-15') . "\n";
Output
Due on 15/10/2026
The pattern captures year, month, and day; the replacement writes them back in day/month/year order.
Formatting a Phone Number
<?php
echo preg_replace('/^(\d{5})(\d{5})$/', '$1 $2', '9876543210') . "\n";
Output
98765 43210
${1} When a Digit Follows
<?php
echo preg_replace('/(\d+)/', '${1}0', 'Price 25') . "\n";
Output
Price 250
Writing $10 would refer to group 10, which does not exist. Braces separate the group number from the digit that follows: ${1}0 means "group 1, then the character 0".
Note: Always write replacement strings in single quotes. In double quotes, PHP processes some of this syntax itself before
preg_replace()sees it:"${1}"is treated as variable interpolation, and"\1"becomes an invisible control character.
Limiting and Counting Replacements
<?php
$result = preg_replace('/a/', 'A', 'banana', 2, $count);
echo "$result ($count replacements)\n";
Output
bAnAna (2 replacements)
Only the first two matches are replaced. $count reports how many replacements happened, which is useful for detecting whether anything changed.
Several Patterns at Once
When $pattern and $replacement are arrays, each pattern is applied in order to the result of the previous one:
<?php
$title = 'Hello World! PHP 8';
$slug = preg_replace(
['/\s+/', '/[^a-z0-9-]/'],
['-', ''],
strtolower($title)
);
echo $slug . "\n";
Output
hello-world-php-8
- Spaces become hyphens:
hello-world!-php-8. - Every character that is not a lowercase letter, digit, or hyphen is removed:
hello-world-php-8.
The order matters: running the second pattern first would remove the spaces before they could become hyphens.
Dynamic Replacements: preg_replace_callback()
When the replacement must be calculated, use preg_replace_callback(). The callback receives the matches array and returns the replacement text:
<?php
$text = 'Pen ₹20, Notebook ₹60, Bag ₹1299';
$withGst = preg_replace_callback(
'/₹(\d+)/u',
fn(array $m): string => '₹' . number_format($m[1] * 1.18, 2),
$text
);
echo $withGst . "\n";
Output
Pen ₹23.60, Notebook ₹70.80, Bag ₹1,532.82
For each price, the callback calculates the amount including 18% GST and formats it. A plain replacement string could not perform arithmetic.
Several Patterns, Several Callbacks: preg_replace_callback_array()
<?php
$message = 'Hi @asha, see #php8 :)';
$html = preg_replace_callback_array([
'/@(\w+)/' => fn(array $m): string => '<a href="/u/' . $m[1] . '">@' . $m[1] . '</a>',
'/#(\w+)/' => fn(array $m): string => '<a href="/tag/' . $m[1] . '">#' . $m[1] . '</a>',
'/:\)/' => fn(array $m): string => '🙂',
], htmlspecialchars($message));
echo $html . "\n";
Output
Hi <a href="/u/asha">@asha</a>, see <a href="/tag/php8">#php8</a> 🙂
- Each pattern has its own callback; they are applied in the order listed.
- The message is escaped with
htmlspecialchars()before links are added, so any HTML typed by the user is neutralized while the generated links stay intact. The captured names contain only word characters (\w), so they are safe to insert.
Masking Sensitive Data
Lookaheads let you replace characters based on what follows them:
<?php
echo preg_replace('/\d(?=\d{4})/', '*', '9876543210') . "\n";
echo preg_replace('/(?<=.).(?=[^@]*@)/', '*', 'asha.k@example.com') . "\n";
Output
******3210
a*****@example.com
\d(?=\d{4})matches a digit only if at least four more digits follow, so every digit except the last four is replaced.- The second pattern replaces every character of the email's local part except the first:
(?<=.)requires a character before it, and(?=[^@]*@)requires that an@still follows.
Error Handling
preg_replace() returns null if an error occurs:
<?php
$result = preg_replace('/\w+/u', 'x', "abc\xFF");
var_dump($result);
echo preg_last_error_msg() . "\n";
Output
NULL
Malformed UTF-8 characters, possibly incorrectly encoded
Passing null on to string functions later causes deprecation notices or type errors. Check the result when the input may be invalid:
$clean = preg_replace($pattern, $replacement, $input)
?? throw new RuntimeException('Regex failed: ' . preg_last_error_msg());
Replacement Strings From Users
In a replacement string, $1 and \1 are interpreted as group references. If the replacement comes from user input, use preg_replace_callback() and return the user's text from the callback — the callback's return value is inserted literally, with no special characters.
Practical Example: Creating URL Slugs
<?php
function slugify(string $title): string
{
$slug = mb_strtolower(trim($title));
$slug = preg_replace("/['’]/u", '', $slug);
$slug = preg_replace('/[^\p{L}\p{M}\p{N}]+/u', '-', $slug);
return trim($slug, '-');
}
echo slugify("PHP 8.5: What's New?") . "\n";
echo slugify(' Café — Résumé Tips ') . "\n";
echo slugify('தமிழ் Tutorial') . "\n";
Output
php-8-5-whats-new
café-résumé-tips
தமிழ்-tutorial
Step-by-Step Explanation
mb_strtolower()lowercases text correctly for all languages, andtrim()removes outer spaces.- Apostrophes are removed first, so
What'sbecomeswhatsinstead ofwhat-s. [^\p{L}\p{M}\p{N}]+matches every run of characters that are not letters, combining marks, or numbers, and replaces it with one hyphen. Including\p{M}keeps Tamil vowel signs attached to their letters.trim($slug, '-')removes hyphens at the start and end (from the?and surrounding spaces).
Modern browsers display Unicode slugs correctly. If your system requires ASCII-only URLs, transliterate first (for example, with the intl extension's Transliterator).
preg_replace() vs str_replace()
str_replace() | preg_replace() | |
|---|---|---|
| Searches for | Fixed text | Patterns |
| Speed | Faster | Slower |
| Reuse parts of the match | No | Yes ($1, …) |
| Calculated replacements | No | With preg_replace_callback() |
| Use when | The text to replace is known exactly | The text follows a pattern |
Common Mistakes
Double-Quoted Patterns and Replacements
Effect: PHP interprets ${1} as a variable and \1 or \d-style escapes in its own way before the regex engine sees the string, producing empty or wrong results.
Fix: Use single quotes for both patterns and replacements.
$10 When You Mean Group 1 Followed by 0
Effect: The wrong group (or nothing) is inserted.
Fix: ${1}0.
Ignoring null Results
Effect: Errors spread as null values.
Fix: Check for null and use preg_last_error_msg().
Using Regex for Fixed Text
Effect: Slower, harder-to-read code.
Fix: Use str_replace().
Building HTML From Unescaped Input
Effect: XSS through replaced content.
Fix: Escape the input first, then add generated HTML with safe captured values.
Best Practices
- Use single-quoted patterns and replacement strings.
- Use
${n}when a group reference is followed by a digit. - Use
preg_replace_callback()for calculated or user-provided replacements. - Apply multiple patterns in a deliberate order with arrays.
- Add the
umodifier for text that may contain non-ASCII characters, and handlenullresults.
Official reference: https\://www.php.net/manual/en/function.preg-replace.php