Signature
preg_replace_callback(
string|array $pattern,
callable $callback,
string|array $subject,
int $limit = -1,
int &$count = null,
int $flags = 0
): string|array|nullPurpose
preg_replace_callback() searches a string with a regular expression and replaces every match with a value your own function computes. It behaves almost like preg_replace(), but instead of a fixed replacement string you supply a callback. This is the right tool whenever the new text depends on the matched text: incrementing numbers, looking up values in a map, changing case selectively, or reformatting a captured fragment.
Signature
Parameters and defaults
$pattern: a pattern string, or an array of pattern strings, including delimiters and modifiers.$callback: any callable. It receives one array of matches and should return the replacement string. A typical handler looks likefunction (array $matches): string.$subject: a string, or an array of strings. With an array, every element is processed and keys are preserved.$limit: the maximum number of replacements per pattern in each subject string. The default-1means no limit.$count: an optional by-reference variable that receives the number of replacements performed.$flags: a combination ofPREG_OFFSET_CAPTUREandPREG_UNMATCHED_AS_NULL, which change the shape of the matches array in the same way as forpreg_match(). The manual lists theflagsparameter as added in PHP 7.4.0.
The matches array
Index 0 holds the whole match, index 1 the first parenthesized group, and so on. Named groups such as (?<word>\w+) also appear under their name. With PREG_OFFSET_CAPTURE, each entry becomes a pair of the text and its byte offset. With PREG_UNMATCHED_AS_NULL, groups that did not participate are reported as null rather than an empty string.
Return value and failure cases
The function returns an array when $subject is an array and a string otherwise. If nothing matches, the subject comes back unchanged. On an error it returns null. If the pattern does not compile into a valid regular expression, PHP emits an E_WARNING. You can inspect runtime PCRE problems with preg_last_error().
Example 1: computing a replacement from a capture
The callback receives two groups, the day and month part and the year. It returns the first group unchanged and the year increased by one.
$text = "Due 04/01/2002 and 12/24/2001";
echo preg_replace_callback(
'|(\d{2}/\d{2}/)(\d{4})|',
fn(array $m): string => $m[1] . ((int) $m[2] + 1),
$text
), "\n";Due 04/01/2003 and 12/24/2002
Example 2: template placeholders and the count
Here the callback looks up each placeholder name in a map. Unknown names return the original match so they stay visible. The $count variable reports how many replacements the engine performed. A callback that returns the original text still counts as a replacement.
$template = "Hello {name}, your plan is {plan}. {missing}";
$values = ['name' => 'Ada', 'plan' => 'Pro'];
$count = 0;
$result = preg_replace_callback(
'/\{(\w+)\}/',
fn(array $m): string => $values[$m[1]] ?? $m[0],
$template,
-1,
$count
);
echo json_encode(['result' => $result, 'count' => $count], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";{"result":"Hello Ada, your plan is Pro. {missing}","count":3}
Example 3: array subjects with a limit
When the subject is an array, the result is an array with the same keys. The limit applies to each element separately, and $count is the total across all elements. Strings without matches are returned untouched.
$subjects = ['a1 b2 c3', 'x9 y8', 'none'];
$count = 0;
$result = preg_replace_callback(
'/\d/',
fn(array $m): string => (string) ($m[0] * 2),
$subjects,
1,
$count
);
echo json_encode(['result' => $result, 'count' => $count], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";{"result":["a2 b2 c3","x18 y8","none"],"count":2}
Edge cases
- Unmatched groups. A group that did not take part in the match may be missing from the array or be an empty string, depending on its position. If you need a predictable shape, use
PREG_UNMATCHED_AS_NULLand test fornull. - Multiple patterns. When
$patternis an array, the patterns run in order, and each one works on the output of the previous one. The same callback is used for all of them. - Empty matches. A pattern that can match an empty string, such as
/x*/, calls your callback at positions where nothing is consumed. Make sure your callback handles an empty$matches[0]. - Recursion. A callback may itself call
preg_replace_callback(). The manual demonstrates a recursive pattern that handles nested BB code this way. - Errors. An invalid pattern gives a warning and a
nullresult, so check fornullbefore you use the value as a string.
Common mistakes
- Forgetting to return. A callback that echoes or has no
returnyields an empty replacement and silently deletes the matched text. - Using the wrong index.
$matches[0]is the whole match, not the first group. Capture groups start at1. - Reading outer variables in a classic closure. A function declared with
functionneedsuse ($values)to see outside variables. An arrow function captures them by value automatically. - Expecting
$countto count changes. It counts replacements performed, even when the callback returns the same text. - Using the callback for a fixed replacement. If the replacement does not depend on the match,
preg_replace()is simpler. - Not escaping user input in patterns. Run user-supplied literals through
preg_quote()before putting them into a pattern.
Related functions
preg_replace()replaces matches with a fixed string or with backreferences.preg_replace_callback_array()applies several pattern and callback pairs in one call.preg_quote()escapes literal text for use in a pattern.preg_last_error()reports the code of the last PCRE error.preg_match()documents the flag behavior used for the matches array.
Recap
Use preg_replace_callback() when each replacement must be calculated from the matched text. The callback gets an array of matches and must return a string. Pass $limit to cap replacements, read $count to see how many occurred, and treat a null return as a failure.
Tested examples
$text = "Due 04/01/2002 and 12/24/2001";
echo preg_replace_callback(
'|(\d{2}/\d{2}/)(\d{4})|',
fn(array $m): string => $m[1] . ((int) $m[2] + 1),
$text
), "\n";Due 04/01/2003 and 12/24/2002
$template = "Hello {name}, your plan is {plan}. {missing}";
$values = ['name' => 'Ada', 'plan' => 'Pro'];
$count = 0;
$result = preg_replace_callback(
'/\{(\w+)\}/',
fn(array $m): string => $values[$m[1]] ?? $m[0],
$template,
-1,
$count
);
echo json_encode(['result' => $result, 'count' => $count], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";{"result":"Hello Ada, your plan is Pro. {missing}","count":3}
$subjects = ['a1 b2 c3', 'x9 y8', 'none'];
$count = 0;
$result = preg_replace_callback(
'/\d/',
fn(array $m): string => (string) ($m[0] * 2),
$subjects,
1,
$count
);
echo json_encode(['result' => $result, 'count' => $count], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";{"result":["a2 b2 c3","x18 y8","none"],"count":2}