01 · THE IDEAREAD → TRY → LEARN
Why use a callback?
A plain replacement string works when every match becomes the same text, or when you can build the result from fixed pieces such as backreferences. Often the new text has to be computed: add one to a year, pad a number, or look a word up in a table. For those jobs PHP offers preg_replace_callback(). It finds every match of a pattern, hands each match to your function, and puts whatever string the function returns in place of the match.
preg_replace_callback(
string|array $pattern,
callable $callback,
string|array $subject,
int $limit = -1,
int &$count = null,
int $flags = 0
): string|array|null
The callback contract
The callback receives one array. Index 0 holds the whole match, index 1 holds the first capture group, index 2 the second, and so on. If you name a group with (?<name>...), the array also has that name as a key. The callback must return a string, and that string is inserted literally. It is not scanned for $1 style references.
The subject can be a string or an array of strings. The return value has the same shape. If the pattern is invalid, a warning is emitted and the result is null. If nothing matches, you get the subject back unchanged.
A first example
The match is always a string, so convert it before doing arithmetic, then convert the result back.
$text = "Release 2024, backport 2023";
echo preg_replace_callback('/\d{4}/', fn(array $m): string => (string) ((int) $m[0] + 1), $text), "\n";Release 2025, backport 2024
Every run of four digits is passed to the arrow function, incremented and returned as text.
Capture groups, limit and count
The fourth argument, $limit, caps the replacements per pattern and per subject. -1 means no cap. The fifth argument is passed by reference and receives the number of replacements made, so initialise a variable before the call.
$text = "cat=3; dog=10";
$count = 0;
$result = preg_replace_callback(
'/([a-z]+)=(\d+)/',
fn(array $m): string => strtoupper($m[1]) . ':' . str_pad($m[2], 3, '0', STR_PAD_LEFT),
$text,
-1,
$count
);
echo json_encode(['text' => $result, 'count' => $count], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";{"text":"CAT:003; DOG:010","count":2}
Here $m[1] is the word and $m[2] is the number. The count is 2 because two matches were replaced.
Lookups with named groups
A very common use is a tiny template engine. The callback looks the token name up in an array. If the name is missing, returning $m[0] leaves the original token in place, which is a safe default.
$values = ['name' => 'Ada', 'role' => 'admin'];
$template = 'Hi {name}, role: {role}, team: {team}';
echo preg_replace_callback(
'/\{(?<key>[a-z]+)\}/',
fn(array $m): string => $values[$m['key']] ?? $m[0],
$template
), "\n";Hi Ada, role: admin, team: {team}
The {team} token has no entry, so the null coalescing operator falls back to the original text.
Pitfalls to watch
- Greedy or too-narrow patterns.
\dmatches one digit, while\d+matches a whole number. Choosing the wrong one changes what the callback sees. For example15would become two separate matches. - Forgetting type conversion. Captured values are strings. Cast them before arithmetic.
- Returning non-strings. The callback is meant to return a string. Be explicit with a return type and a cast.
- Replacement is not re-scanned. The text you return is not matched again, so a callback cannot accidentally loop.
- Unchecked failures. A broken pattern yields
null. In real code, check for it before using the result.
Exercise
Write double_numbers(string $text): string. Every run of consecutive digits in the text must be replaced by its value multiplied by two, and all other characters stay as they are. Leading zeros disappear because the value is converted to an integer, so 007 becomes 14. Think about which quantifier makes a whole number a single match, and remember that the callback receives an array, not a string.
Recap
Use preg_replace_callback() when replacement text must be calculated from the match. Read the whole match from $m[0], groups from numeric or named keys, return a string, and use $limit and $count when you need to control or measure the work. Return $m[0] to leave a match unchanged.
Functions · Arrays · Types
- Replace matched tokens using deterministic callbacks
- Implement and test a deterministic PHP 8.4 function