Multibyte strings7 min readExamples run on PHP 8.5.10

mb_ereg_replace_callback()

Learn how mb_ereg_replace_callback() replaces multibyte regex matches with the value returned by a callback, with examples, edge cases and common mistakes.

Signature

mb_ereg_replace_callback(
    string $pattern,
    callable $callback,
    string $string,
    ?string $options = null
): string|false|null
Available since PHP 5.4.17.48.08.18.28.38.48.5All versions →

Purpose

mb_ereg_replace_callback() searches a string for matches of a multibyte-aware regular expression and replaces each match with whatever a callback returns. Use it when a fixed replacement string is not enough: the new text has to be computed from the matched text, for example by incrementing a number, measuring a word or normalising a spelling.

It behaves almost like mb_ereg_replace(), except that you supply a callback instead of a replacement string. It belongs to the mb_ereg* family, which uses its own pattern syntax and is separate from the PCRE preg_* functions.

Signature

Parameters and defaults

  • $pattern: the regular expression. It is written without delimiters, and it may contain multibyte characters.
  • $callback: any PHP callable. It receives one array of matched elements. Index 0 is the whole match and 1, 2 and so on are the parenthesised groups. It should return the replacement string.
  • $string: the subject text to scan.
  • $options: optional search option letters, such as i for case-insensitive matching. The default is null; see mb_regex_set_options() for the available letters. options has been nullable since PHP 8.0.0.

The character encoding used is the internal encoding or the one set with mb_regex_encoding(). Set it explicitly when your text is UTF-8 so the result does not depend on configuration.

Return value and failure cases

On success you get the resulting string. If the text contains no matches, you get the subject back unchanged. The function returns false on error. If $string is not valid for the current encoding, it returns null. Because the return type is a union, compare strictly (=== null, === false) rather than relying on truthiness, since an empty string is a legitimate successful result.

Example 1: computing a replacement

The callback receives the groups, so it can do arithmetic on one of them and keep the rest.

Example
<?php
$text = "Due 04/01/2002 and 12/24/2001";
echo mb_ereg_replace_callback(
    '(\d{2}/\d{2}/)(\d{4})',
    fn(array $m): string => $m[1] . ((int) $m[2] + 1),
    $text
), "\n";
Output
Due 04/01/2003 and 12/24/2002

Each date keeps its day and month prefix while the year is increased by one.

Example 2: multibyte words

With the encoding set to UTF-8, a word pattern treats accented letters as single characters, and mb_strlen() in the callback counts characters instead of bytes.

Example
<?php
mb_regex_encoding('UTF-8');
$text = 'naïve café';
echo mb_ereg_replace_callback(
    '\w+',
    fn(array $m): string => '[' . $m[0] . ':' . mb_strlen($m[0], 'UTF-8') . ']',
    $text
), "\n";
Output
[naïve:5] [café:4]

Example 3: options and a counter

The i option makes matching case-insensitive. A closure can capture a variable by reference to record how many replacements happened.

Example
<?php
$text = 'php, Php and PHP';
$count = 0;
$result = mb_ereg_replace_callback(
    'php',
    function (array $m) use (&$count): string {
        $count++;
        return 'PHP';
    },
    $text,
    'i'
);
echo json_encode(['text' => $result, 'count' => $count]), "\n";
Output
{"text":"PHP, PHP and PHP","count":3}

Edge cases

  • A pattern that matches nothing leaves the subject untouched, and the callback is never called.
  • The callback runs once per match, from left to right, so keep it cheap and free of side effects you cannot repeat.
  • Group numbers follow the order of opening parentheses. If you add a group at the start of a pattern, the indexes you use in the callback shift.
  • Invalid input for the active encoding produces null, not a partially converted string.

Common mistakes

  1. Using PCRE delimiters. Writing '/\d+/' treats the slashes as literal characters. Write '\d+' instead.
  2. Passing a replacement string. The second argument must be callable. A plain string is interpreted as a function name, which is almost never what you want.
  3. Forgetting to return. A callback that echoes or has no return does not provide the replacement text you intended.
  4. Ignoring encoding. Without a deliberate encoding, byte-based assumptions creep in. Call mb_regex_encoding('UTF-8') and use mb_* string functions inside the callback.
  5. Loose result checks. Test for false and null strictly before using the result as text.
  • mb_ereg_replace() for a fixed replacement string with the same pattern syntax.
  • mb_regex_encoding() to get or set the regex encoding.
  • mb_regex_set_options() to see the option letters.
  • preg_replace_callback() for the PCRE equivalent.

Recap

mb_ereg_replace_callback() replaces every match of a delimiter-free, multibyte-aware pattern with the string your callback returns. The callback receives an array of the whole match and its groups. The function returns the new string, false on error, or null for input that is invalid in the current encoding, so check the result strictly.

Tested examples

Increment the year in each matched date
<?php
$text = "Due 04/01/2002 and 12/24/2001";
echo mb_ereg_replace_callback(
    '(\d{2}/\d{2}/)(\d{4})',
    fn(array $m): string => $m[1] . ((int) $m[2] + 1),
    $text
), "\n";
Output
Due 04/01/2003 and 12/24/2002
Annotate multibyte words with their character counts
<?php
mb_regex_encoding('UTF-8');
$text = 'naïve café';
echo mb_ereg_replace_callback(
    '\w+',
    fn(array $m): string => '[' . $m[0] . ':' . mb_strlen($m[0], 'UTF-8') . ']',
    $text
), "\n";
Output
[naïve:5] [café:4]
Case-insensitive option with a replacement counter
<?php
$text = 'php, Php and PHP';
$count = 0;
$result = mb_ereg_replace_callback(
    'php',
    function (array $m) use (&$count): string {
        $count++;
        return 'PHP';
    },
    $text,
    'i'
);
echo json_encode(['text' => $result, 'count' => $count]), "\n";
Output
{"text":"PHP, PHP and PHP","count":3}