Multibyte strings7 min readExamples run on PHP 8.5.10

mb_rtrim()

Learn how PHP 8.4 mb_rtrim() removes Unicode whitespace or custom characters from the end of a string without splitting multibyte characters.

Signature

mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null): string
Available since PHP 8.4.07.48.08.18.28.38.48.5All versions →

Purpose

mb_rtrim() removes characters from the end of a string. It is the multibyte-aware counterpart of rtrim(). The older function works on bytes, so it can misbehave when text contains characters that take more than one byte in UTF-8. mb_rtrim() reads the string as characters in the chosen encoding, so it only removes whole characters. The function is available from PHP 8.4.0.

Typical uses are cleaning user input, tidying imported text, and removing trailing punctuation from titles, labels or captions that may contain accented letters, CJK text or typographic symbols.

Signature

Parameters and defaults

  • $string is the input text.
  • $characters is optional and defaults to null. With null, the function strips a built-in set of whitespace characters. That set includes the ordinary space, tab, line feed, carriage return, NUL byte, vertical tab and form feed. It also includes many Unicode spaces, such as no-break space U+00A0, the quad and em/en spaces U+2000 to U+200A, line separator U+2028, paragraph separator U+2029, narrow no-break space U+202F, medium mathematical space U+205F, ideographic space U+3000, next line U+0085 and Mongolian vowel separator U+180E.
  • If you pass a string, you list the characters to strip. Pass the characters themselves, one after another. The list replaces the default whitespace set, so whitespace is no longer trimmed unless you include it.
  • $encoding is optional and defaults to null. In that case PHP uses the internal character encoding. Passing 'UTF-8' explicitly makes the code independent of that setting.

Return value and failures

The function returns the modified string. If nothing at the end matches, you get the same text back. If every character matches, you get an empty string. Only the end is touched, so leading and interior characters are never removed. As with other mbstring functions, passing an unsupported encoding name is an error you should treat as a programming mistake rather than something to recover from.

Example 1: default whitespace removal

The default set covers more than ASCII spaces. Here an ideographic space, a no-break space, a normal space and a newline are all removed. A string made only of Unicode spaces becomes empty, and leading spaces survive.

Example
<?php
$inputs = [
    'padded' => "Café\u{3000}\u{00A0} \n",
    'blank' => "\u{2003}\u{3000}",
    'leading' => "  keep  ",
];
$out = [];
foreach ($inputs as $key => $value) {
    $out[$key] = mb_rtrim($value, null, 'UTF-8');
}
echo json_encode($out, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
{"padded":"Café","blank":"","leading":"  keep"}

Example 2: trimming custom characters

When you supply $characters, each character in that string is a candidate for removal, including multibyte ones such as the ellipsis. Trimming stops at the first character from the end that is not in the list.

Example
<?php
$title = "価格: 100円!!!…";
echo json_encode(mb_rtrim($title, "!…", 'UTF-8'), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
"価格: 100円"

Example 3: why byte-based rtrim() can be unsafe

The letter ä is encoded in UTF-8 as bytes C3 A4. The currency sign ¤ is encoded as C2 A4. They share the final byte. Plain rtrim() sees the byte A4 in the list and removes it, leaving a broken half character. mb_rtrim() compares whole characters and keeps ä. The example prints hexadecimal bytes so the broken value is visible.

Example
<?php
$text = "\u{00E4}";
$remove = "\u{00A4}";
echo json_encode([
    'rtrim' => bin2hex(rtrim($text, $remove)),
    'mb_rtrim' => bin2hex(mb_rtrim($text, $remove, 'UTF-8')),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
{"rtrim":"c3","mb_rtrim":"c3a4"}

Edge cases

  • Invisible characters that are not in the default set. The documented list does not include the zero-width space U+200B, so a string ending in it is not shortened by default. If you need that behaviour, pass it in $characters yourself.
  • A custom list replaces the default. mb_rtrim($s, "!") will not remove trailing spaces. Include the whitespace characters in the list if you want both.
  • Empty input. An empty string stays an empty string.
  • Only the end changes. Use mb_ltrim() for the start, or mb_trim() for both sides.

Common mistakes

  1. Expecting trimmed spaces inside the string. Interior whitespace is never removed. Use a replacement function for that.
  2. Using rtrim() on UTF-8 text with a custom character list. Byte matching can damage multibyte characters, as the third example shows.
  3. Assuming a range syntax. The documentation describes the list as the characters to strip, so write each character you need rather than relying on any shorthand.
  4. Forgetting the encoding. If your data is not in the internal encoding, pass the correct $encoding so the text is read the way you intend.
  5. Running it on PHP older than 8.4. The function does not exist there, so you need a fallback or a regular expression.
  • mb_trim() strips from both ends.
  • mb_ltrim() strips from the beginning.
  • rtrim() is the byte-based version for single-byte or ASCII-only work.

Recap

mb_rtrim() safely removes trailing Unicode whitespace or a chosen set of characters from the end of a string. It returns a string, leaves the start and middle untouched, replaces the default whitespace set when you give your own list, and avoids the byte-level corruption that rtrim() can cause with multibyte text.

Tested examples

Default Unicode whitespace removal
<?php
$inputs = [
    'padded' => "Café\u{3000}\u{00A0} \n",
    'blank' => "\u{2003}\u{3000}",
    'leading' => "  keep  ",
];
$out = [];
foreach ($inputs as $key => $value) {
    $out[$key] = mb_rtrim($value, null, 'UTF-8');
}
echo json_encode($out, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
{"padded":"Café","blank":"","leading":"  keep"}
Trimming custom multibyte characters
<?php
$title = "価格: 100円!!!…";
echo json_encode(mb_rtrim($title, "!…", 'UTF-8'), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
"価格: 100円"
Comparing rtrim() and mb_rtrim()
<?php
$text = "\u{00E4}";
$remove = "\u{00A4}";
echo json_encode([
    'rtrim' => bin2hex(rtrim($text, $remove)),
    'mb_rtrim' => bin2hex(mb_rtrim($text, $remove, 'UTF-8')),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
{"rtrim":"c3","mb_rtrim":"c3a4"}