Regular expressions7 min readExamples run on PHP 8.5.10

preg_replace_callback_array()

Learn how preg_replace_callback_array() runs several regex patterns, each with its own callback, over one string or an array of strings in PHP 8.4.

Signature

preg_replace_callback_array(
    array $pattern,
    string|array $subject,
    int $limit = -1,
    int &$count = null,
    int $flags = 0
): string|array|null

Purpose

preg_replace_callback_array() performs several regular expression replacements in one call. You pass an associative array in which each key is a pattern and each value is a callable. For every match of a pattern, PHP calls the matching callable and puts the string it returns in place of the match. It behaves like preg_replace_callback(), except that each pattern has its own callback. This keeps a group of related text transformations in a single readable table.

Signature

Parameters and defaults

  • $pattern: an associative array that maps pattern strings (keys) to callables (values). Each key needs delimiters, such as /\d+/.
  • $subject: a string, or an array of strings, to search.
  • $limit: the maximum number of replacements for each pattern in each subject string. The default -1 means no limit.
  • $count: an optional variable passed by reference. It receives the number of replacements made.
  • $flags: a combination of PREG_OFFSET_CAPTURE and PREG_UNMATCHED_AS_NULL. These change the format of the matches array that each callback receives, as described for preg_match(). The $flags parameter was added in PHP 7.4.0.

Each callback receives one argument, an array of matches. Index 0 holds the full match and later indexes hold capture groups. The callback should return the replacement string.

Return value and failure cases

The function returns an array when $subject is an array and a string otherwise. If nothing matches, you get the subject back unchanged. On an error the function returns null. An invalid pattern also emits an E_WARNING. Use preg_last_error() when you need to know why a run failed. Check with === null, because an empty string is a valid result.

Example 1: several patterns, several callbacks

The first pattern doubles every number, and the second uppercases a trailing word.

Example
<?php
$text = 'Price: 5 USD, tax: 2 USD, code: abc';
$result = preg_replace_callback_array(
    [
        '/\d+/' => fn(array $m): string => (string) ($m[0] * 2),
        '/[a-z]+$/' => fn(array $m): string => strtoupper($m[0]),
    ],
    $text
);
echo $result, PHP_EOL;
Output
Price: 10 USD, tax: 4 USD, code: ABC

Example 2: patterns run in order

Patterns are applied one after another, in array order. Each pattern sees the output of the previous one. Here the first pattern turns every cat into dog, so the second pattern then finds three words to uppercase. The $count value adds up replacements across all patterns: two from the first pattern and three from the second.

Example
<?php
$count = 0;
$result = preg_replace_callback_array(
    [
        '/\bcat\b/' => fn(array $m): string => 'dog',
        '/\bdog\b/' => fn(array $m): string => strtoupper($m[0]),
    ],
    'cat dog cat',
    -1,
    $count
);
echo json_encode(['result' => $result, 'count' => $count], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), PHP_EOL;
Output
{"result":"DOG DOG DOG","count":5}

Example 3: an array subject with a limit

When the subject is an array, the function processes each string and returns an array. The $limit applies to each pattern in each string, so a limit of 1 replaces only the first digit in every element.

Example
<?php
$items = ['a1b2', 'x9y8', 'none'];
$result = preg_replace_callback_array(
    ['/\d/' => fn(array $m): string => '<' . $m[0] . '>'],
    $items,
    1,
    $count
);
echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), PHP_EOL;
echo $count, PHP_EOL;
Output
["a<1>b2","x<9>y8","none"]
2

Edge cases

  • Order matters. A later pattern can match text that an earlier callback produced. If the patterns must not interact, design them so their output cannot match each other, or run separate calls.
  • Limit is per pattern and per subject. It is not a global cap, so a call with several patterns can make more replacements than $limit.
  • Count is cumulative. $count reports the total across every pattern and every subject element.
  • No match means no change. The subject comes back as it was, and $count is 0.
  • Flags change the callback input. With PREG_OFFSET_CAPTURE, each entry in the matches array becomes a pair of text and byte offset. Your callback must then read $m[0][0] instead of $m[0].

Common mistakes

  1. Forgetting to return a value. A callback that only echoes, like a debugging callback, gives the match an empty replacement. Always return the string you want in the result.
  2. Using a list instead of an associative array. The pattern must be the key and the callable the value. Putting patterns in values will not work.
  3. Missing delimiters. Each key is a full PCRE pattern, so \d+ fails where /\d+/ works. An invalid pattern raises a warning and the result is null.
  4. Assuming the result is always a string. Calling strlen() on a null result after a regex error causes follow-on problems. Test for null first.
  5. Treating numeric input as numbers. Matches are always strings. Cast them, or let arithmetic convert them, and cast the callback's result back to a string.
  6. Reading a capture group that did not match. Without PREG_UNMATCHED_AS_NULL, unmatched trailing groups may be missing from the array. Check with isset() or use the flag.
  • preg_replace_callback() uses one pattern, or a list of patterns, with a single shared callback.
  • preg_replace() replaces matches with a replacement string instead of a callback.
  • preg_quote() escapes literal text so it can be embedded in a pattern.
  • preg_last_error() reports the error code from the last PCRE call.

Recap

Use preg_replace_callback_array() when different patterns need different replacement logic. Key each pattern to its callable. Remember that patterns run in order, that $limit is applied per pattern and per subject, and that $count accumulates. Always return strings from callbacks, and compare the result to null to detect failure.

Tested examples

Several patterns with separate callbacks
<?php
$text = 'Price: 5 USD, tax: 2 USD, code: abc';
$result = preg_replace_callback_array(
    [
        '/\d+/' => fn(array $m): string => (string) ($m[0] * 2),
        '/[a-z]+$/' => fn(array $m): string => strtoupper($m[0]),
    ],
    $text
);
echo $result, PHP_EOL;
Output
Price: 10 USD, tax: 4 USD, code: ABC
Patterns run in order and count accumulates
<?php
$count = 0;
$result = preg_replace_callback_array(
    [
        '/\bcat\b/' => fn(array $m): string => 'dog',
        '/\bdog\b/' => fn(array $m): string => strtoupper($m[0]),
    ],
    'cat dog cat',
    -1,
    $count
);
echo json_encode(['result' => $result, 'count' => $count], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), PHP_EOL;
Output
{"result":"DOG DOG DOG","count":5}
Array subject with a per-string limit
<?php
$items = ['a1b2', 'x9y8', 'none'];
$result = preg_replace_callback_array(
    ['/\d/' => fn(array $m): string => '<' . $m[0] . '>'],
    $items,
    1,
    $count
);
echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), PHP_EOL;
echo $count, PHP_EOL;
Output
["a<1>b2","x<9>y8","none"]
2