Multibyte strings7 min readExamples run on PHP 8.5.10

mb_ereg_search_pos()

Learn how mb_ereg_search_pos() reports the byte offset and byte length of the next multibyte regex match, with examples, edge cases and common mistakes.

Signature

mb_ereg_search_pos(?string $pattern = null, ?string $options = null): array|false
Available since PHP 4.2.07.48.08.18.28.38.48.5All versions →

Purpose

mb_ereg_search_pos() runs a multibyte-aware regular expression against a string that you registered earlier with mb_ereg_search_init(). It does not return the matched text. It returns where the next match is: a byte offset and a byte length. Each successful call moves an internal cursor past the match, so repeated calls walk through the subject string one match at a time.

The function belongs to the mb_ereg_* family, which uses the Oniguruma engine rather than PCRE. Patterns are written without delimiters.

Signature

Parameters and defaults

  • $pattern: the regular expression, without delimiters. If you pass null (the default), the pattern given to mb_ereg_search_init() is reused.
  • $options: option letters that control matching, such as case-insensitivity. They are the same letters accepted by mb_regex_set_options(). With null, the current options stay in effect.

The subject string is not a parameter. It comes from the earlier mb_ereg_search_init() call, and if you do not initialise a new one, the previous string is used.

The character encoding is the one set with mb_regex_encoding(), or the internal encoding if you have not set one. Setting it to UTF-8 explicitly makes your code easier to reason about.

Return value and failure

On success you get a two-element array: [offset, length]. Both numbers are measured in bytes, not characters. The offset is relative to the start of the whole subject string.

On failure the function returns false. In practice this is what you see when there are no more matches, and it is also what you get when an error occurs. Always compare with !== false, because [0, 3] is a valid, non-empty array but code that tests loosely can still go wrong in other places.

Example 1: a first match in UTF-8 text

The string below contains ë and é, which take two bytes each in UTF-8. The word café therefore starts at byte 11, not at character 10, and is five bytes long.

Example
<?php
mb_regex_encoding('UTF-8');
mb_ereg_search_init('Zoë loves café');
$pos = mb_ereg_search_pos('café');
echo json_encode($pos, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
[11,5]

Example 2: walking through all matches

Because the cursor advances after each hit, a loop can collect every match. Passing no arguments reuses the pattern from mb_ereg_search_init(). The loop ends when the function returns false.

Example
<?php
mb_regex_encoding('UTF-8');
mb_ereg_search_init('a1 bb22 ccc333', '\\d+');
$found = [];
while (($m = mb_ereg_search_pos()) !== false) {
    $found[] = $m;
}
echo json_encode($found, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
[[1,1],[5,2],[11,3]]

Example 3: converting bytes to characters, and a miss

If you need a character position, count the characters in the part of the string before the byte offset. A pattern that does not occur produces false.

Example
<?php
mb_regex_encoding('UTF-8');
$text = 'été à Paris';
mb_ereg_search_init($text);
$pos = mb_ereg_search_pos('Paris');
$chars = mb_strlen(substr($text, 0, $pos[0]), 'UTF-8');

mb_ereg_search_init('abc');
$missing = mb_ereg_search_pos('x');

echo json_encode(['charOffset' => $chars, 'missing' => $missing], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
{"charOffset":6,"missing":false}

Edge cases

  • Cursor state. The position is global state for the mbstring regex search functions. Calling mb_ereg_search_init() again resets it, so initialise before each independent scan.
  • Empty matches. A pattern that can match an empty string, such as x*, may return a length of 0. Guard against loops that never make progress.
  • No match is also false. The return value does not distinguish a clean end of matches from other failures, so do not rely on it to detect an invalid pattern.
  • Options. Passing $options changes the matching behaviour for that call. Check mb_regex_set_options() for the letters it accepts.

Common mistakes

  1. Treating the numbers as characters. The offset and length are in bytes. With accented letters or CJK text they differ from mb_strlen() counts. Use mb_strcut() or substr() with these values, not mb_substr().
  2. Forgetting to call mb_ereg_search_init(). Without it there is no subject string, or you accidentally search a stale one from earlier code.
  3. Adding delimiters. Write '\\d+', not '/\\d+/'. The slashes would be treated as literal characters.
  4. Using a loose check in a loop. Use !== false to end the loop.
  5. Expecting the matched text. If you want the text itself, use mb_ereg_search_regs() or mb_ereg_search(), or cut the string using the returned pair.
  • mb_ereg_search_init() sets the subject string and optionally the pattern.
  • mb_ereg_search() only tells you whether a match exists.
  • mb_ereg_search_regs() returns the matched text and groups.
  • mb_ereg_search_getpos() and mb_ereg_search_setpos() read and change the cursor.
  • mb_regex_encoding() and mb_regex_set_options() control encoding and options.

Recap

mb_ereg_search_pos() finds the next match of a multibyte regex in the string prepared by mb_ereg_search_init() and returns [byteOffset, byteLength], or false when nothing more matches. Remember that both numbers count bytes, that the cursor advances after each success, and that you should reset it by re-initialising before a fresh scan.

Tested examples

A first match in UTF-8 text
<?php
mb_regex_encoding('UTF-8');
mb_ereg_search_init('Zoë loves café');
$pos = mb_ereg_search_pos('café');
echo json_encode($pos, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
[11,5]
Walking through all matches
<?php
mb_regex_encoding('UTF-8');
mb_ereg_search_init('a1 bb22 ccc333', '\\d+');
$found = [];
while (($m = mb_ereg_search_pos()) !== false) {
    $found[] = $m;
}
echo json_encode($found, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
[[1,1],[5,2],[11,3]]
Converting bytes to characters, and a miss
<?php
mb_regex_encoding('UTF-8');
$text = 'été à Paris';
mb_ereg_search_init($text);
$pos = mb_ereg_search_pos('Paris');
$chars = mb_strlen(substr($text, 0, $pos[0]), 'UTF-8');

mb_ereg_search_init('abc');
$missing = mb_ereg_search_pos('x');

echo json_encode(['charOffset' => $chars, 'missing' => $missing], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
{"charOffset":6,"missing":false}