skip to content

In PHP, when do you use preg_replace_callback() instead of preg_replace(), and how do $1, ${1} and \1 references work in a replacement?

level: middleimportance: should knowfreq 45%

answer

  1. replacement string versus computed value
  2. $n, \n and ${n} backreferences
  3. ${1}1 avoids the eleventh group
  4. callback receives the matches array
  5. null on failure

basics

~20 s

preg_replace() substitutes a template in which $1, \1 or ${1} insert captured groups; preg_replace_callback() calls a function with the matches array and uses its return value, for replacements that need logic such as zero-padding or a lookup.

solid answer

~40 s

`preg_replace($pattern, $replacement, $subject, $limit = -1, &$count = null)` replaces every match with a **template**: `$n` or `\n` inserts group *n* (0 to 99), and `${n}` separates the number from following digits, so `${1}1` is group 1 then a literal `1`, while `$11` would mean group 11. `preg_replace_callback($pattern, $callback, $subject, ...)` instead calls `$callback(array $matches): string` for every match and inserts what it returns. Use it whenever the replacement needs **computation**: normalizing `ord 2026/45` to `ORD-2026-000045` with `sprintf()`, looking something up, or inserting user-controlled text literally without template parsing. Both return the new string, or `null` on failure, and take a `$limit` and a by-reference `$count`. `preg_replace_callback_array()` maps several patterns to callbacks. The old `e` modifier, which evaluated the replacement as code, was removed in PHP 7.0.

code

php · 19 lines
php
<?php
declare(strict_types=1);

$body = 'Refs: ord 2026/123, ORD 2026/45';
$pattern = '/\bord\s*(\d{4})\/(\d{1,6})\b/i';

echo preg_replace($pattern, 'ORD-$1-${2}', $body), "\n";
// Refs: ORD-2026-123, ORD-2026-45

$normalized = preg_replace_callback(
    $pattern,
    fn (array $m): string => sprintf('ORD-%s-%06d', $m[1], (int) $m[2]),
    $body,
);
if ($normalized === null) {
    throw new RuntimeException(preg_last_error_msg());
}
echo $normalized, "\n";
// Refs: ORD-2026-000123, ORD-2026-000045

go deeper

for a junior

Recall that preg_replace uses $1 or \1 for captured groups and that preg_replace_callback lets a function build the replacement.

for a middle

Explain the ${n} form, why single quotes help, the callback's matches array with named groups, and the null failure value.

for a senior

Show you would move logic and user data into callbacks, check for null before overwriting the subject, and migrate any legacy e-modifier code.

for a principal

Judge when a chain of regex replacements should become a proper parser or templating step, and set review rules for replacement code that handles user data.

## Two ways to build the replacement PHP offers two families for regex replacement: - **`preg_replace(string|array $pattern, string|array $replacement, string|array $subject, int $limit = -1, &$count = null): string|array|null`** takes a replacement **template**. - **`preg_replace_callback(string|array $pattern, callable $callback, string|array $subject, int $limit = -1, &$count = null, int $flags = 0): string|array|null`** takes a **function** that computes each replacement. Both return the modified subject, the subject unchanged when nothing matched, or `null` when an error occurred. `$limit` caps replacements per subject (`-1` means unlimited) and `$count` receives the number made. ## Backreferences in a template Inside the replacement string of `preg_replace()`: | Syntax | Meaning | |---|---| | `$0` or `\0` | the whole match | | `$1` ... `$99`, `\1` ... `\99` | the text of that capture group | | `${1}` | group 1, delimited so that digits may follow | The braces matter when a digit follows the reference. `'${1}1'` is group 1 followed by the character `1`; `'$11'` and `'\11'` would refer to group 11. A group that did not participate inserts an empty string. Two quoting details trip people up: 1. In a **double-quoted** PHP string, `$1` is not a variable (variable names cannot start with a digit), but `\1` becomes a control character before PCRE sees it. Single-quoted replacements are safer. 2. Named groups are **not** available in the template by name; use their number, or switch to a callback where `$m['name']` works. ## When a callback is the right tool A template can only rearrange captured text. As soon as the replacement needs logic, use `preg_replace_callback()`: - **Formatting**: support staff write `ord 2026/45`; the canonical form is `ORD-2026-000045`, which needs `sprintf('%06d', ...)`. - **Lookups**: replace a reference with the customer-facing order number from a map. - **Conditional output**: leave unknown references untouched by returning `$m[0]`. - **Literal insertion**: text returned by the callback is inserted as is, so a `$` or backslash in data cannot be misread as a backreference. The callback receives the same array `preg_match()` would produce, including named groups, and must return a `string`. Since PHP 7.4 the `$flags` parameter accepts `PREG_OFFSET_CAPTURE` and `PREG_UNMATCHED_AS_NULL`, which change the shape of that array. ## Several patterns at once Both functions accept **arrays** of patterns; they are applied one after another to the evolving subject, so an earlier replacement can create text a later pattern matches. `preg_replace_callback_array(array $pattern, string|array $subject, ...)` takes a map of pattern to callback and applies them in order, which keeps multi-rule normalizers readable. ## The removed e modifier Older code used `preg_replace('/.../e', 'strtoupper("$1")', $s)`, where the `e` modifier evaluated the replacement as PHP code after substituting the captures. Because the captured text came from input, it was a code-injection vector. It was **removed in PHP 7.0**; the modifier now triggers `Unknown modifier 'e'` and the call returns `null`. Every such use translates directly to `preg_replace_callback()` with a closure. ## Handling failure A `null` return is easy to miss: assigned back to `$body`, it wipes the email text, and with `strict_types` it causes a `TypeError` as soon as the variable is passed to a `string` parameter. Check the result: ```php $normalized = preg_replace_callback($pattern, $fn, $body); if ($normalized === null) { throw new RuntimeException(preg_last_error_msg()); } ``` ## Choosing quickly 1. Fixed text or simple rearrangement of groups: `preg_replace()` with single-quoted `$n` references. 2. Any computation, lookup or literal user data: `preg_replace_callback()`. 3. Several independent rules: `preg_replace_callback_array()`. 4. A literal substring with no pattern at all: plain string replacement is cheaper. ## What interviewers listen for The template syntax is the easy half; interviewers probe the edges. They expect you to explain why `${1}1` exists, why single quotes are safer for replacements, and why named groups need a callback. They also look for security awareness: data inserted through a template can be misread as a backreference, and the removed `e` modifier is the historical example of replacement text becoming code. Finally, a senior answer handles the `null` failure value instead of assigning it over the original text. Mentioning `preg_replace_callback_array()` for multi-rule normalizers is a bonus rather than a requirement.

  • Why write ${1}1 instead of $11 in a preg_replace() replacement?
    `$11` and `\11` are read as a reference to group 11. When you want group 1 followed by a literal `1`, the braces end the reference: `${1}1`. The same applies to any digit that directly follows a backreference.
  • How do you insert text that may contain $ or backslashes as a literal replacement?
    Return it from a `preg_replace_callback()` callback. The callback's return value is inserted verbatim, while a `preg_replace()` template would interpret `$1` or `\1` inside the data as backreferences.
  • What does preg_replace() return when the pattern hits the backtrack limit?
    `null`, not the original string. `preg_last_error()` then returns `PREG_BACKTRACK_LIMIT_ERROR`. Assigning the result back to the subject variable without a check destroys the text, so compare with `=== null` first.

saying these in an interview costs you the question

  • Named groups can be referenced by name in a preg_replace template.
  • $11 in a replacement always means group 1 followed by the digit 1.
  • preg_replace returns the unchanged subject when an error occurs.
  • The e modifier is still the shortest way to compute a replacement.
  • preg_replace_callback passes only the whole match string to the callback.