Multibyte strings7 min readExamples run on PHP 8.5.10

mb_ereg_search_getpos()

Learn how mb_ereg_search_getpos() reports the byte offset where the next multibyte regex search will start, with tested PHP 8.4 examples.

Signature

mb_ereg_search_getpos(): int
Available since PHP 4.2.07.48.08.18.28.38.48.5All versions →

Purpose

mb_ereg_search_getpos() tells you where the next incremental multibyte regular expression search will begin. The mb_ereg_search* family keeps hidden state: a subject string, a pattern and a current position. You load the subject with mb_ereg_search_init(), then call mb_ereg_search(), mb_ereg_search_pos() or mb_ereg_search_regs() repeatedly. Each successful search moves the cursor forward. mb_ereg_search_getpos() lets you read that cursor.

The most important detail is the unit: the position is counted in bytes from the start of the string, not in characters. With UTF-8 text containing non-ASCII letters, the byte offset can be larger than the character offset.

Signature

Parameters and defaults

The function takes no parameters, so there are no defaults. It reads the state created by earlier calls. The character encoding used is the current multibyte regex encoding, which you can set with mb_regex_encoding(). Setting it explicitly to UTF-8 makes scripts easier to understand.

Return value and failure cases

The function returns an int: the byte offset at which the next mb_ereg_search(), mb_ereg_search_pos() or mb_ereg_search_regs() call will start matching. Right after mb_ereg_search_init() the cursor is at the beginning of the new string. After a successful match it sits after the matched text. The manual documents no error return and no exceptions for this function. Call mb_ereg_search_init() first so the value refers to the string you expect.

Example 1: before and after a match

The subject below contains Japanese characters. Each of those characters takes three bytes in UTF-8, so the offset after matching PHP is not the character count.

Example
<?php
mb_regex_encoding('UTF-8');
mb_ereg_search_init('日本 PHP 語', '[A-Z]+');
$before = mb_ereg_search_getpos();
mb_ereg_search();
$after = mb_ereg_search_getpos();
echo json_encode(['before' => $before, 'after' => $after]), "\n";
Output
{"before":0,"after":10}

The cursor starts at 0. The text 日本 uses 7 bytes and PHP uses 3 more, so the cursor ends at 10.

Example 2: tracking progress through repeated matches

Because the cursor advances on every successful search, you can record the position after each match. Here mb_ereg_search_regs() returns the match groups as an array, or false when nothing more matches.

Example
<?php
mb_regex_encoding('UTF-8');
mb_ereg_search_init('ab ñ cd', '[a-z]+');
$found = [];
while ($match = mb_ereg_search_regs()) {
    $found[] = [$match[0], mb_ereg_search_getpos()];
}
echo json_encode($found, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
[["ab",2],["cd",8]]

The letter ñ is outside [a-z], so it is skipped. It occupies two bytes, which is why the second match ends at byte 8 rather than 7.

Example 3: pairing with mb_ereg_search_setpos()

mb_ereg_search_setpos() moves the cursor and mb_ereg_search_getpos() reads it back. This is useful for resuming a scan from a known byte offset.

Example
<?php
mb_regex_encoding('UTF-8');
mb_ereg_search_init('日本語 abc', '[a-z]+');
mb_ereg_search_setpos(10);
$before = mb_ereg_search_getpos();
mb_ereg_search();
$after = mb_ereg_search_getpos();
echo json_encode(['before' => $before, 'after' => $after]), "\n";
Output
{"before":10,"after":13}

The three Japanese characters use 9 bytes and the space is byte 9, so abc begins at offset 10. After matching it, the cursor is at 13.

Edge cases

  • Bytes versus characters. To turn a byte offset into a character count, take the prefix with substr($subject, 0, $pos) and pass it to mb_strlen(). This is only safe when the offset falls on a character boundary.
  • Reinitializing. Calling mb_ereg_search_init() again installs a new subject, so a position read earlier no longer refers to the current string.
  • Shared state. The cursor is global to the multibyte regex search state, not tied to a variable. Interleaving two scans in one request will confuse both of them.
  • Empty matches. Patterns that can match zero characters may not advance the cursor in the way you expect, so prefer patterns that consume text when you loop.

Common mistakes

  1. Treating the result as a character index and using it with mb_substr(). The value is in bytes, so use substr() or mb_strcut() for byte-based slicing.
  2. Calling the function without having called mb_ereg_search_init() and then assuming it describes your string.
  3. Expecting it to return the start of the last match. It reports where the next search starts, which for a successful match is the end of the previous one. Use mb_ereg_search_pos() when you need the start and length of a match.
  4. Forgetting that the regex encoding affects matching. Set it with mb_regex_encoding() rather than relying on defaults.
  • mb_ereg_search_init() sets the subject string and optionally the pattern.
  • mb_ereg_search_setpos() sets the start point for the next match.
  • mb_ereg_search(), mb_ereg_search_pos() and mb_ereg_search_regs() run the search and move the cursor.
  • mb_regex_encoding() gets or sets the regex character encoding.

Recap

mb_ereg_search_getpos() has no parameters and returns an int byte offset for the next incremental multibyte regex search. It starts at the beginning after initialization, moves forward after successful matches, and can be changed with mb_ereg_search_setpos(). Remember that it counts bytes, so convert carefully when you need character positions.

Tested examples

Cursor before and after a match in UTF-8 text
<?php
mb_regex_encoding('UTF-8');
mb_ereg_search_init('日本 PHP 語', '[A-Z]+');
$before = mb_ereg_search_getpos();
mb_ereg_search();
$after = mb_ereg_search_getpos();
echo json_encode(['before' => $before, 'after' => $after]), "\n";
Output
{"before":0,"after":10}
Recording the cursor after each match
<?php
mb_regex_encoding('UTF-8');
mb_ereg_search_init('ab ñ cd', '[a-z]+');
$found = [];
while ($match = mb_ereg_search_regs()) {
    $found[] = [$match[0], mb_ereg_search_getpos()];
}
echo json_encode($found, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
[["ab",2],["cd",8]]
Setting and reading the position
<?php
mb_regex_encoding('UTF-8');
mb_ereg_search_init('日本語 abc', '[a-z]+');
mb_ereg_search_setpos(10);
$before = mb_ereg_search_getpos();
mb_ereg_search();
$after = mb_ereg_search_getpos();
echo json_encode(['before' => $before, 'after' => $after]), "\n";
Output
{"before":10,"after":13}