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?
answer
- replacement string versus computed value
- $n, \n and ${n} backreferences
- ${1}1 avoids the eleventh group
- callback receives the matches array
- null on failure
basics
~20 spreg_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
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-000045go deeper
Recall that preg_replace uses $1 or \1 for captured groups and that preg_replace_callback lets a function build the replacement.
Explain the ${n} form, why single quotes help, the callback's matches array with named groups, and the null failure value.
Show you would move logic and user data into callbacks, check for null before overwriting the subject, and migrate any legacy e-modifier code.
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.