Signature
mb_stripos(
string $haystack,
string $needle,
int $offset = 0,
?string $encoding = null
): int|falsePurpose
mb_stripos() answers a simple question: where does the first occurrence of a piece of text begin inside a larger string, ignoring letter case? Unlike stripos(), it counts characters in the chosen encoding, not bytes. That makes it the right tool for UTF-8 text such as Café, Ünal or Crème Brûlée, where one visible letter can take more than one byte.
Its case-sensitive sibling is mb_strpos(). If you only deal with plain ASCII, stripos() is enough. Once accented or non-Latin letters appear, the multibyte version gives positions that match what a reader sees.
Signature
Parameters and defaults
$haystackis the string to search in.$needleis the text to look for. Since PHP 8.0 an empty needle is accepted.$offsetis the character position at which the search starts. It defaults to0. A negative offset counts back from the end of the haystack.$encodingnames the character encoding, for example'UTF-8'. If it is omitted ornull, the internal encoding setting is used.
Passing the encoding explicitly makes your code independent of configuration, which is useful when the same function runs in different environments.
Return value and failure cases
The function returns the zero-based character position of the first match as an int, or false when the needle does not occur.
Two situations raise a ValueError:
- the
$offsetis greater than the length of the haystack; - the
$encodingis not a valid encoding name (this has been an exception since PHP 8.0; before that it was a warning).
A needle that is merely absent is not an error; you get false.
Example 1: accented letters, ignoring case
The positions below are character positions. The needle BRÛLÉE is written in capitals, yet it matches Brûlée in the haystack.
$text = 'Crème Brûlée and Café';
echo json_encode([
mb_stripos($text, 'BRÛLÉE', 0, 'UTF-8'),
mb_stripos($text, 'café', 0, 'UTF-8'),
mb_stripos($text, 'tea', 0, 'UTF-8'),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";[6,17,false]
The third lookup finds nothing, so it yields false.
Example 2: using an offset
The offset lets you skip earlier matches. A positive value starts at that character index; a negative value starts that many characters before the end.
$text = 'Tea, tea, TEA';
echo json_encode([
mb_stripos($text, 'tea', 1, 'UTF-8'),
mb_stripos($text, 'tea', 6, 'UTF-8'),
mb_stripos($text, 'tea', -3, 'UTF-8'),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";[5,10,10]
Starting at index 1 skips the first Tea and finds the lowercase one at 5. Starting at 6 or at -3 lands on the final uppercase TEA at 10.
Example 3: strict checks, empty needle and errors
A match at the very start returns 0, which is falsy. Always compare with === or !== against false.
function findWord(string $haystack, string $needle): string
{
$position = mb_stripos($haystack, $needle, 0, 'UTF-8');
return $position === false ? 'missing' : 'at ' . $position;
}
echo findWord('Alpha', 'al'), "\n";
echo findWord('Alpha', 'z'), "\n";
echo json_encode(mb_stripos('abc', '', 0, 'UTF-8')), "\n";
try {
mb_stripos('abc', 'a', 4, 'UTF-8');
} catch (ValueError $e) {
echo get_class($e), "\n";
}at 0 missing 0 ValueError
The empty needle matches immediately at position 0. The offset 4 is beyond the three-character haystack, so a ValueError is thrown.
Edge cases
- Offset equal to the length is not larger than the haystack, so it does not trigger the offset error; only values greater than the length do.
- Negative offsets have been supported since PHP 7.1. A negative offset that reaches past the start is not the same as a too-large positive offset, so test your boundaries.
- Mismatched encoding: if the bytes are not valid in the declared encoding, positions can be misleading. Make sure the string and the
$encodingargument agree. - Case rules are those implemented by the mbstring extension for the given encoding. For ordinary letters in common scripts they behave as expected, but avoid relying on unusual case mappings for logic that must be exact.
Common mistakes
- Writing
if (mb_stripos($s, $n))and treating a match at index 0 as a failure. Use!== false. - Mixing byte offsets from
strpos()orstrlen()with character offsets frommb_stripos(). Stay inside themb_*family when slicing, for example withmb_substr(). - Omitting the encoding and assuming a default that differs between environments.
- Using
stripos()on UTF-8 text and then wondering why accented capitals are not matched or positions are too large. - Forgetting that a large offset throws instead of quietly returning
false.
Related functions
mb_strpos()for the case-sensitive version.stripos()andstrpos()for byte-based searching.mb_substr()to extract text using the position you found.
Recap
mb_stripos() returns the character index of the first case-insensitive match, or false. Give it an explicit encoding, compare the result strictly with false, and keep the offset within the haystack length to avoid a ValueError.
Tested examples
$text = 'Crème Brûlée and Café';
echo json_encode([
mb_stripos($text, 'BRÛLÉE', 0, 'UTF-8'),
mb_stripos($text, 'café', 0, 'UTF-8'),
mb_stripos($text, 'tea', 0, 'UTF-8'),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";[6,17,false]
$text = 'Tea, tea, TEA';
echo json_encode([
mb_stripos($text, 'tea', 1, 'UTF-8'),
mb_stripos($text, 'tea', 6, 'UTF-8'),
mb_stripos($text, 'tea', -3, 'UTF-8'),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";[5,10,10]
function findWord(string $haystack, string $needle): string
{
$position = mb_stripos($haystack, $needle, 0, 'UTF-8');
return $position === false ? 'missing' : 'at ' . $position;
}
echo findWord('Alpha', 'al'), "\n";
echo findWord('Alpha', 'z'), "\n";
echo json_encode(mb_stripos('abc', '', 0, 'UTF-8')), "\n";
try {
mb_stripos('abc', 'a', 4, 'UTF-8');
} catch (ValueError $e) {
echo get_class($e), "\n";
}at 0 missing 0 ValueError