Multibyte strings7 min readExamples run on PHP 8.5.10

mb_ereg_search()

Learn how mb_ereg_search() tests a string prepared with mb_ereg_search_init() against a multibyte regex and advances an internal position on each match.

Signature

mb_ereg_search(?string $pattern = null, ?string $options = null): bool
Available since PHP 4.2.07.48.08.18.28.38.48.5All versions →

Purpose

mb_ereg_search() runs a multibyte-aware regular expression against a string that was stored earlier by mb_ereg_search_init(). It returns only a boolean: did the pattern match somewhere at or after the current search position? Each successful match moves an internal position to the end of that match, so repeated calls walk through the subject from left to right.

This makes it a stateful, cursor-style API. It differs from mb_ereg(), which takes the subject on every call and does not remember where it stopped. Use mb_ereg_search() when you want to scan one string for several matches in sequence, and use the companion functions to learn what matched and where.

Signature

The function has been available since PHP 4.2.0. As of PHP 8.0.0, both $pattern and $options are nullable.

Parameters and defaults

  • $pattern: the regular expression, written without delimiters. When it is null (the default), the pattern from the previous call or from mb_ereg_search_init() is reused.
  • $options: option letters such as i for case-insensitive matching. The letters are explained under mb_regex_set_options(). When it is null, the default options apply.

The subject string is not a parameter. It comes from mb_ereg_search_init().

The character encoding is the internal encoding, or the one set with mb_regex_encoding(). Setting the encoding explicitly keeps your code independent of configuration.

Return value and failure cases

The function returns true when the string matches and false otherwise. A false result simply means no further match exists from the current position. After a miss, the position moves to the end of the string, so another call also returns false until you reset the position by calling mb_ereg_search_init() again.

To get details of a successful match, call a companion function:

  • mb_ereg_search_getregs() returns the matched text and capture groups of the last match.
  • mb_ereg_search_getpos() returns the current position, measured in bytes.
  • mb_ereg_search_setpos() moves the position manually.

Example 1: collecting every word

The loop below calls mb_ereg_search() until it returns false. After each hit, mb_ereg_search_getregs() supplies the matched text, with index 0 being the whole match.

Example
<?php
mb_regex_encoding('UTF-8');
mb_ereg_search_init('café tea über', '\w+');
$words = [];
while (mb_ereg_search()) {
    $match = mb_ereg_search_getregs();
    $words[] = $match[0];
}
echo json_encode($words, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
["café","tea","über"]

Example 2: watching the byte position

The position is counted in bytes, not characters. In the string below, ñ takes two bytes in UTF-8, so the first match ends at byte 3 rather than 2. The second match ends at byte 7, the full byte length of the subject.

Example
<?php
mb_regex_encoding('UTF-8');
mb_ereg_search_init('añb ñ', 'ñ');
$first = mb_ereg_search();
$pos1 = mb_ereg_search_getpos();
$second = mb_ereg_search();
$pos2 = mb_ereg_search_getpos();
$third = mb_ereg_search();
echo json_encode(['first' => $first, 'pos1' => $pos1, 'second' => $second, 'pos2' => $pos2, 'third' => $third]), "\n";
Output
{"first":true,"pos1":3,"second":true,"pos2":7,"third":false}

Example 3: passing a pattern and options

Here the initialisation supplies only the subject. The first search provides the pattern and the i option. Later calls pass nothing and reuse that pattern.

Example
<?php
mb_regex_encoding('UTF-8');
mb_ereg_search_init('Alpha beta ALPHA');
$count = 0;
if (mb_ereg_search('alpha', 'i')) {
    $count++;
    while (mb_ereg_search()) {
        $count++;
    }
}
echo json_encode(['count' => $count]), "\n";
Output
{"count":2}

Edge cases

  • Positions are byte offsets. If you need a character offset, convert it yourself, for example by measuring the prefix with mb_strlen().
  • A pattern that can match an empty string needs care in loops, because a match that consumes nothing may not move the cursor forward the way you expect. Prefer patterns that consume at least one character.
  • Patterns have no delimiters and no trailing flags. Pass flags through $options.
  • The search state is global to the process. Interleaving two scans of different strings will interfere with each other, so finish one scan before starting another.

Common mistakes

  1. Skipping mb_ereg_search_init(). The function has no subject of its own. Always set the string first.
  2. Expecting match text as a return value. The return value is a boolean. Fetch text with mb_ereg_search_getregs() or positions with mb_ereg_search_pos().
  3. Treating positions as characters. They are bytes, which differ for any non-ASCII text.
  4. Adding PCRE-style delimiters. Writing /alpha/i makes the slashes part of the pattern.
  5. Forgetting to reinitialise. Once a scan ends, only a new mb_ereg_search_init() call, or mb_ereg_search_setpos(), lets you search the same string again.
  6. Assuming the encoding. Call mb_regex_encoding() so behaviour does not depend on configuration.
  • mb_ereg_search_init() sets the subject and optionally the pattern.
  • mb_ereg_search_pos() and mb_ereg_search_regs() search and return position or groups directly.
  • mb_ereg_search_getregs(), mb_ereg_search_getpos() and mb_ereg_search_setpos() inspect and control the state.
  • mb_regex_encoding() and mb_regex_set_options() control encoding and options.
  • mb_ereg() performs a one-off match without a stored cursor.

Recap

mb_ereg_search() is a boolean, stateful matcher. Initialise the subject, call it repeatedly, and read match details through its companion functions. Remember that positions are bytes, that null arguments reuse earlier settings, and that a failed search exhausts the string until you reinitialise it.

Tested examples

Collect every word with a search loop
<?php
mb_regex_encoding('UTF-8');
mb_ereg_search_init('café tea über', '\w+');
$words = [];
while (mb_ereg_search()) {
    $match = mb_ereg_search_getregs();
    $words[] = $match[0];
}
echo json_encode($words, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
["café","tea","über"]
Track the byte position after each match
<?php
mb_regex_encoding('UTF-8');
mb_ereg_search_init('añb ñ', 'ñ');
$first = mb_ereg_search();
$pos1 = mb_ereg_search_getpos();
$second = mb_ereg_search();
$pos2 = mb_ereg_search_getpos();
$third = mb_ereg_search();
echo json_encode(['first' => $first, 'pos1' => $pos1, 'second' => $second, 'pos2' => $pos2, 'third' => $third]), "\n";
Output
{"first":true,"pos1":3,"second":true,"pos2":7,"third":false}
Supply a pattern and options, then reuse them
<?php
mb_regex_encoding('UTF-8');
mb_ereg_search_init('Alpha beta ALPHA');
$count = 0;
if (mb_ereg_search('alpha', 'i')) {
    $count++;
    while (mb_ereg_search()) {
        $count++;
    }
}
echo json_encode(['count' => $count]), "\n";
Output
{"count":2}