Multibyte strings7 min readExamples run on PHP 8.5.10

mb_strstr()

Learn how mb_strstr() returns part of a string before or after the first match, how it handles encodings, and which mistakes to avoid.

Signature

mb_strstr(
    string $haystack,
    string $needle,
    bool $before_needle = false,
    ?string $encoding = null
): string|false
Available since PHP 5.2.07.48.08.18.28.38.48.5All versions →

Purpose

mb_strstr() searches a string for the first occurrence of another string and returns a slice of the original. It is the multibyte-aware counterpart of strstr(). You would use it to cut text at a separator such as @, / or : when the text may contain characters outside ASCII, such as accented Latin letters, Japanese or Cyrillic.

The function works in terms of characters in the chosen encoding, not raw bytes. That matters when the slice boundary must never fall in the middle of a character.

Signature

Parameters and defaults

  • $haystack: the string to search in.
  • $needle: the string to look for. Since PHP 8.0.0 an empty string is accepted for this parameter.
  • $before_needle: defaults to false. With false, the result starts at the first match and runs to the end of the haystack, and the match is included. With true, the result is everything from the start up to the first match, and the match is excluded.
  • $encoding: defaults to null, which means the current internal encoding is used. Passing an explicit name such as 'UTF-8' makes the call independent of configuration, which is a good habit for code that must behave the same everywhere.

Return value and failure cases

The function returns a string containing the selected portion, or false when $needle does not occur in $haystack. Since PHP 8.0.0, an invalid $encoding name throws a ValueError; older versions emitted a warning instead.

Because the return type is a union of string and false, always distinguish failure with === false. A successful result can be a non-empty string, but the loose comparison habits of older code can still mislead you in conditions.

Example 1: split an address around the at sign

The first call keeps the separator and the text after it. The second call asks for the part before the separator.

Example
<?php
$email = 'zoë@example.com';
echo json_encode(mb_strstr($email, '@', false, 'UTF-8'), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
echo json_encode(mb_strstr($email, '@', true, 'UTF-8'), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
"@example.com"
"zoë"

Example 2: handling a missing needle

When the needle is absent, you get the boolean false, not an empty string. Check the result before using it as text.

Example
<?php
$text = 'Crème brûlée';
$result = mb_strstr($text, 'tarte', false, 'UTF-8');
var_dump($result);
$found = mb_strstr($text, 'brû', false, 'UTF-8');
echo json_encode($found, JSON_UNESCAPED_UNICODE), "\n";
Output
bool(false)
"brûlée"

Example 3: character boundaries and case sensitivity

The split on / keeps every Japanese character intact. The final line shows that matching is case-sensitive: lowercase ä does not match uppercase Ä.

Example
<?php
$path = '日本/東京/渋谷';
$first = mb_strstr($path, '/', true, 'UTF-8');
$rest = mb_strstr($path, '/', false, 'UTF-8');
echo json_encode([$first, $rest], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
echo json_encode(mb_strstr('Ärger', 'ä', false, 'UTF-8'), JSON_UNESCAPED_UNICODE), "\n";
Output
["日本","/東京/渋谷"]
false

Edge cases

  • Only the first match counts. In 日本/東京/渋谷, the function stops at the first /. Later separators stay inside the returned tail.
  • Case sensitivity. mb_strstr() compares exactly. For a case-insensitive search use mb_stristr().
  • Empty needle. PHP 8.0.0 made an empty $needle acceptable, so do not rely on older behaviour from before that version.
  • Invalid byte sequences. The manual notes that in PHP 8.3.2 the portion returned with $before_needle set to true for invalid strings, and for SJIS-mac strings, is determined from the same character indices as other mbstring functions. Cleaning or validating input first is still the safest approach.
  • Wrong encoding. If the declared encoding does not match the real bytes of the data, the character positions are not meaningful. Pass the encoding that actually describes the input.

Common mistakes

  1. Treating false as text. Concatenating or printing the result without a check hides a failed search. Test with === false first.
  2. Using strstr() on multibyte text. It works on bytes. For simple, valid UTF-8 searches it can seem fine, but it ignores the encoding you intend, so use the mb function when character semantics are required.
  3. Expecting the needle to be removed by default. With the default false, the needle is part of the result. If you want only what follows it, trim the needle's length with mb_substr() and mb_strlen(), or use a position-based function instead.
  4. Mixing up the flag. true means the part before the match, not after it.
  5. Relying on the internal encoding. If you omit $encoding, behaviour depends on configuration. An explicit value makes the code predictable.
  6. Passing an unsupported encoding name. This throws a ValueError in PHP 8, so validate encoding names that come from outside your code.
  • mb_stristr(): the case-insensitive variant.
  • strstr() and stristr(): byte-oriented equivalents.
  • mb_strpos(): returns a character position instead of a slice.
  • mb_substr(): extracts a slice by character offset and length.

Recap

mb_strstr() returns the part of $haystack that starts at the first occurrence of $needle, or the part before it when $before_needle is true. It respects character boundaries in the chosen encoding, matches case-sensitively, and returns false when nothing is found. Pass an explicit encoding, compare the result with === false, and choose mb_stristr() when case should be ignored.

Tested examples

Split an address around the at sign
<?php
$email = 'zoë@example.com';
echo json_encode(mb_strstr($email, '@', false, 'UTF-8'), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
echo json_encode(mb_strstr($email, '@', true, 'UTF-8'), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
"@example.com"
"zoë"
Handling a missing needle
<?php
$text = 'Crème brûlée';
$result = mb_strstr($text, 'tarte', false, 'UTF-8');
var_dump($result);
$found = mb_strstr($text, 'brû', false, 'UTF-8');
echo json_encode($found, JSON_UNESCAPED_UNICODE), "\n";
Output
bool(false)
"brûlée"
Character boundaries and case sensitivity
<?php
$path = '日本/東京/渋谷';
$first = mb_strstr($path, '/', true, 'UTF-8');
$rest = mb_strstr($path, '/', false, 'UTF-8');
echo json_encode([$first, $rest], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
echo json_encode(mb_strstr('Ärger', 'ä', false, 'UTF-8'), JSON_UNESCAPED_UNICODE), "\n";
Output
["日本","/東京/渋谷"]
false