Regular expressions7 min readExamples run on PHP 8.5.10

preg_replace()

Learn how preg_replace() searches strings with regular expressions, rewrites matches with backreferences, handles arrays, limits and counts, and where it commonly goes wrong.

Signature

preg_replace(
    string|array $pattern,
    string|array $replacement,
    string|array $subject,
    int $limit = -1,
    int &$count = null
): string|array|null

Purpose

preg_replace() searches one or more strings for matches of a Perl-compatible regular expression and replaces each match with new text. Use it when the text to find is described by a pattern, such as runs of whitespace, a word followed by digits, or an e-mail-like token. When you only need to replace an exact piece of text, str_replace() or str_ireplace() is simpler and avoids pattern syntax entirely.

Signature

Parameters and defaults

  • $pattern: a delimited pattern such as '/\s+/', optionally with modifiers after the closing delimiter. It can also be an array of patterns, which are applied one after another.
  • $replacement: the text that replaces each match. It may contain references to captured groups, written as $1 or \1; the $n form is preferred. $0 or \0 is the whole match. Groups are numbered by their opening parenthesis, from left to right. If a literal digit must follow a reference, isolate the reference with braces, as in ${1}1.
  • $subject: the string to search, or an array of strings. With an array, every entry is processed and the result is an array. Keys of an associative array are preserved.
  • $limit: the maximum number of replacements for each pattern in each subject string. The default -1 means no limit.
  • $count: an optional by-reference variable that receives the total number of replacements made.

Return value and failure cases

The function returns a string when $subject is a string, and an array when $subject is an array. If nothing matches, the subject comes back unchanged. If an error occurs, the return value is null. A pattern that does not compile produces an E_WARNING, and so does the removed e modifier. Always compare against null explicitly when the input might trigger a regex failure, and use preg_last_error() to find out what happened.

Example 1: backreferences and the count

The captured word before the @ is reused in the replacement through $1, and $count reports how many substitutions took place.

Example
<?php
$count = 0;
$result = preg_replace('/(\w+)@example\.com/', '$1 at example dot com', 'Mail ana@example.com or bo@example.com', -1, $count);
echo json_encode(['result' => $result, 'count' => $count], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
echo "\n";
Output
{"result":"Mail ana at example dot com or bo at example dot com","count":2}

Example 2: collapsing whitespace and using a limit

The first call normalises every run of whitespace. The second stops after two replacements, so the later gap is left untouched.

Example
<?php
$text = "alpha   beta \t gamma    delta";
echo json_encode([
    preg_replace('/\s+/', ' ', $text),
    preg_replace('/\s+/', '-', $text, 2),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
echo "\n";
Output
["alpha beta gamma delta","alpha-beta-gamma    delta"]

Example 3: pattern arrays and subject arrays

When both $pattern and $replacement are arrays, the pairs run sequentially. Each pair works on the output of the previous pair, not on the original string. The subject here is an associative array, so its keys survive.

Example
<?php
$subjects = ['first' => 'a', 'second' => 'cab'];
$patterns = ['/a/', '/b/', '/c/'];
$replacements = ['b', 'c', 'd'];
echo json_encode(preg_replace($patterns, $replacements, $subjects), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
echo "\n";
Output
{"first":"d","second":"ddd"}

The string a becomes b, then c, then d. That chain effect is why swapping two values in a single call does not work. Use an intermediate placeholder if you need a swap.

Edge cases

  • The $limit applies per pattern and per subject string, not to the whole call. Three patterns with a limit of 1 can produce up to three replacements in one string.
  • If the replacement array has fewer elements than the pattern array, the extra patterns are replaced with an empty string.
  • Array pairs are processed in the order the keys appear, not in numeric index order. If you build arrays with explicit indexes, call ksort() on both before using them.
  • A reference such as \11 is ambiguous. Write ${1}1 when you mean group 1 followed by the digit 1.
  • Single-quoted PHP strings keep backslashes, so '\d' reaches the regex engine as \d. In a replacement, a literal backslash or dollar sign may need extra escaping.
  • Nothing matching is not an error. Check $count, not the return value, to learn whether anything changed.

Common mistakes

  1. Forgetting the delimiters, for example passing '\d+' instead of '/\d+/'. This triggers a warning and a null result.
  2. Treating the result as always a string. A failed run yields null, which can silently turn into an empty string when echoed.
  3. Using preg_replace() for plain literal text, which is slower and invites escaping errors.
  4. Expecting parallel replacement from pattern arrays, when they are applied sequentially.
  5. Putting user input directly into a pattern without preg_quote(), so special characters change the meaning of the regex.
  6. Needing logic in the replacement. A static replacement string cannot compute anything, and preg_replace_callback() is the right tool there.
  • preg_replace_callback() computes each replacement with a function.
  • preg_filter() is similar but returns only subjects where something matched.
  • preg_match() tests for a match and extracts groups.
  • preg_split() splits by a pattern.
  • preg_quote() escapes text for use inside a pattern.
  • preg_last_error() reports the last PCRE error code.
  • str_replace() replaces exact substrings.

Recap

preg_replace() rewrites every match of a regular expression, up to an optional limit, and can report the number of replacements through $count. It accepts strings or arrays for all three main arguments, applies pattern arrays in sequence, preserves array keys, and returns null on failure. Reach for it when the target is a pattern, and choose simpler string functions when it is not.

Tested examples

Backreferences and the replacement count
<?php
$count = 0;
$result = preg_replace('/(\w+)@example\.com/', '$1 at example dot com', 'Mail ana@example.com or bo@example.com', -1, $count);
echo json_encode(['result' => $result, 'count' => $count], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
echo "\n";
Output
{"result":"Mail ana at example dot com or bo at example dot com","count":2}
Collapsing whitespace and using a limit
<?php
$text = "alpha   beta \t gamma    delta";
echo json_encode([
    preg_replace('/\s+/', ' ', $text),
    preg_replace('/\s+/', '-', $text, 2),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
echo "\n";
Output
["alpha beta gamma delta","alpha-beta-gamma    delta"]
Pattern arrays and subject arrays
<?php
$subjects = ['first' => 'a', 'second' => 'cab'];
$patterns = ['/a/', '/b/', '/c/'];
$replacements = ['b', 'c', 'd'];
echo json_encode(preg_replace($patterns, $replacements, $subjects), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
echo "\n";
Output
{"first":"d","second":"ddd"}