Multibyte strings7 min readExamples run on PHP 8.5.10

mb_strrchr()

Learn how mb_strrchr() returns the part of a multibyte string at or before the last match of a needle, with PHP 8.4 examples, edge cases and common mistakes.

Signature

mb_strrchr(
    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_strrchr() looks for the last occurrence of a needle inside a haystack and returns a slice of the haystack built around that position. It is the multibyte-aware relative of strrchr(). Because it understands character encodings, it never cuts a character such as é or → in half, which a byte-oriented search can do when the data is UTF-8.

Typical uses are taking the last segment of a delimited value, such as a file name after the final slash, or taking everything before the final delimiter, such as the directory part.

Signature

Parameters and defaults

  • $haystack: the string to search.
  • $needle: the string to find. Despite the word "character" in the function's short description, the needle is a string, so a multi-character delimiter such as :: is searched as a whole. Since PHP 8.0 an empty needle is accepted.
  • $before_needle: defaults to false. With false you get the text from the last match to the end of the haystack, and the match itself is included. With true you get the text from the start of the haystack up to the last match, and the match is excluded.
  • $encoding: defaults to null, which means the current internal encoding is used. Passing 'UTF-8' explicitly makes your code independent of configuration.

Return value and failure cases

The function returns a string with the requested portion. If the needle is not found it returns false, not an empty string. Since PHP 8.0 an invalid encoding name throws a ValueError; before 8.0 it produced a warning instead.

Example: tail and head around the last slash

The first example splits a path at its last /. The default mode keeps the slash at the start of the result, and true returns what comes before it.

Example
<?php
$path = 'docs/café/menu/été.txt';
echo mb_strrchr($path, '/', false, 'UTF-8'), "\n";
echo mb_strrchr($path, '/', true, 'UTF-8'), "\n";
Output
/été.txt
docs/café/menu

Example: a missing needle returns false

When nothing matches, the result is the boolean false. A multibyte needle such as an arrow works like any other string.

Example
<?php
$result = mb_strrchr('naïve', 'z', false, 'UTF-8');
var_dump($result);
$tag = mb_strrchr('a→b→c', '→', false, 'UTF-8');
echo json_encode($tag, JSON_UNESCAPED_UNICODE), "\n";
Output
bool(false)
"→c"

Example: a multi-character delimiter

The needle can be longer than one character. Only the final :: counts, so earlier delimiters stay in the head part.

Example
<?php
$text = 'one::två::tre';
echo json_encode([
    'tail' => mb_strrchr($text, '::', false, 'UTF-8'),
    'head' => mb_strrchr($text, '::', true, 'UTF-8'),
], JSON_UNESCAPED_UNICODE), "\n";
Output
{"tail":"::tre","head":"one::två"}

Edge cases

  • Match included or excluded. With the default before_needle = false the needle is the first thing in the result. If you want only the text after the delimiter, you must remove the delimiter yourself, for example with mb_substr() and mb_strlen($needle, 'UTF-8').
  • Only the last match matters. If the needle occurs many times, earlier occurrences are ignored in the tail result and kept in the head result.
  • Case sensitivity. The search is case-sensitive. For a case-insensitive variant use mb_strrichr().
  • Empty haystack. Searching an empty string for a non-empty needle yields false.
  • Encoding mismatch. If the string is stored in an encoding different from the one you pass, the match boundaries can be wrong. Be consistent about the encoding of your data.
  • Invalid encoding name. A name PHP does not recognize raises ValueError in PHP 8, so wrap user-supplied encoding names in validation or a try block.

Common mistakes

  1. Testing the result with a loose check. An if (mb_strrchr(...)) test treats a result such as "0" as false. Compare with === false instead.
  2. Expecting an empty string when nothing is found. The not-found value is false, which can silently turn into "" when concatenated or echoed.
  3. Forgetting that the needle is kept. Beginners often expect /file.txt to arrive as file.txt.
  4. Mixing up the flag. true means "give me the part before the needle", not "search from the end"; the search always goes for the last occurrence.
  5. Relying on the internal encoding. If the default encoding differs between environments, behavior can differ too. Pass 'UTF-8' when your data is UTF-8.
  6. Using the byte function on multibyte text. strrchr() operates on bytes, so with non-ASCII needles it is the wrong tool when character boundaries matter.
  • strrchr(): the byte-based version.
  • mb_strstr(): finds the first occurrence and returns a portion of the string.
  • mb_strrichr(): the case-insensitive last-occurrence variant.
  • mb_strrpos(): returns the character position of the last match instead of a slice.
  • mb_substr(): extracts a slice by character offset, useful for trimming the delimiter.

Recap

mb_strrchr() finds the last occurrence of a needle in a multibyte string and returns either the tail starting at the match or, with before_needle = true, the head before it. It returns false when there is no match, so compare strictly. Pass an explicit encoding, remember that the needle is included in the tail, and reach for the related functions when you need case-insensitivity or a numeric position.

Tested examples

Split a path at its last slash
<?php
$path = 'docs/café/menu/été.txt';
echo mb_strrchr($path, '/', false, 'UTF-8'), "\n";
echo mb_strrchr($path, '/', true, 'UTF-8'), "\n";
Output
/été.txt
docs/café/menu
A missing needle returns false
<?php
$result = mb_strrchr('naïve', 'z', false, 'UTF-8');
var_dump($result);
$tag = mb_strrchr('a→b→c', '→', false, 'UTF-8');
echo json_encode($tag, JSON_UNESCAPED_UNICODE), "\n";
Output
bool(false)
"→c"
A multi-character delimiter
<?php
$text = 'one::två::tre';
echo json_encode([
    'tail' => mb_strrchr($text, '::', false, 'UTF-8'),
    'head' => mb_strrchr($text, '::', true, 'UTF-8'),
], JSON_UNESCAPED_UNICODE), "\n";
Output
{"tail":"::tre","head":"one::två"}