mb_stripos
Finds the character position of the first case-insensitive match of a substring in a string.
mb_stripos(string $haystack, string $needle, int $offset = 0, ?string $encoding = null): int|false- Added in PHP 5.2.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$haystack | string | The string to search in. |
$needle | string | The string to search for. May be empty as of PHP 8.0. |
$offsetoptional | int= 0 | Character position to start searching from. Negative counts back from the end of the haystack. |
$encodingoptional | ?string= null | Character encoding. Null uses the internal encoding. |
Return value
int|falseThe character position of the first match, counted from 0, or false if the needle is not found.
Errors
Throws a ValueError if offset lies outside the haystack, or if encoding is not a valid encoding.
Changelog
- PHP 8.0.0needle now accepts an empty string.
- PHP 8.0.0encoding is nullable now.
- PHP 7.1.0Support for negative offsets was added.
mb_stripos() is mb_strpos() with case folding applied to both operands first. Two things make it different from plain stripos(): the returned position counts characters, not bytes, and the case folding is Unicode-aware, so É matches é, Ü matches ü and Greek or Cyrillic pairs fold correctly. stripos() only folds ASCII A–Z.
When to reach for it
Use it when you need the position of a case-insensitive match in text that may be non-ASCII — slicing a header off a message, finding where a keyword starts so you can highlight it, or splitting a line at the first occurrence of a marker. If you only need to know whether the needle is present, mb_stristr() !== false or a lowercased str_contains() reads better.
What trips people up
The classic bug is the falsy zero. A match at the very start returns 0, which if (!mb_stripos(...)) treats exactly like "not found". Always compare with !== false.
The other trap is mixing units: the position you get back is a character index, so feed it to mb_substr(), never to substr(). Doing the latter silently produces garbage the moment an accent appears earlier in the string.
Example
When to use it
- Locate a case-insensitive keyword in a subject line so you can slice off the prefix
- Highlight a search term in accented text by finding its offset and rebuilding the string
- Detect and strip a case-varying protocol or label prefix from user input
Frequently asked questions
What is the difference between mb_stripos and stripos?
mb_stripos returns a character offset and folds case using Unicode rules, so É matches é. stripos returns a byte offset and only folds ASCII letters, which makes it wrong for accented or non-Latin text.
Why does if (mb_stripos($h, $n)) fail when the match is at the start?
Because a match at position 0 returns int(0), which PHP treats as false in a boolean test. Compare the result with !== false instead.
Can I pass the result of mb_stripos to substr?
No. mb_stripos returns a character index, so pair it with mb_substr. Mixing it with the byte-based substr produces corrupt output as soon as the string contains a multibyte character.
More examples
Slicing with the returned offset
mb_stripos returns a character index. Feeding it to mb_substr gives 'Grüße aus München'; feeding the same number to substr starts one byte too early because 'ü' earlier in the line... in fact the byte string is longer, so substr starts inside the wrong region and prints a mangled result.
<?php
mb_internal_encoding('UTF-8');
$line = 'Betreff: Grüße aus München';
$pos = mb_stripos($line, 'grüße');
if ($pos !== false) {
echo mb_substr($line, $pos), "\n"; // correct
echo substr($line, $pos), "\n"; // wrong: mixes chars and bytes
}Searching from an offset
The first call finds the capital R at position 5. Starting at 6 skips it and finds the lowercase r at 9. A negative offset of -5 starts the search five characters from the end, which still catches that same r at 9.
<?php
mb_internal_encoding('UTF-8');
$h = 'Café Ristretto';
var_dump(mb_stripos($h, 'r')); // first r
var_dump(mb_stripos($h, 'r', 6)); // skip past it
var_dump(mb_stripos($h, 'r', -5)); // last 5 characters only
Discussion