mb_substr
Extracts part of a string by character position, safely for multibyte encodings.
mb_substr(string $string, int $start, ?int $length = null, ?string $encoding = null): string- Added in PHP 4.0.6
Parameters
| Parameter | Type | Description |
|---|---|---|
$string | string | The string to extract from. |
$start | int | Character offset to start at. Negative values count back from the end of the string. |
$lengthoptional | ?int= null | Maximum number of characters to return. A negative value stops that many characters from the end. Null means run to the end of the string. |
$encodingoptional | ?string= null | Character encoding of the string. Falls back to the internal encoding when null. |
Return value
stringThe portion of the string specified by start and length, or an empty string when the range selects nothing.
Errors
Throws a ValueError if encoding names an unknown character encoding.
Changelog
- PHP 8.4.0On strings containing encoding errors, character indices are now interpreted the same way as in other mbstring functions, so an offset from mb_strpos() can be passed straight in.
- PHP 8.0.0encoding is nullable now.
mb_substr() slices a string by character offset. substr() slices by byte offset, and cutting a UTF-8 string mid-sequence leaves a broken byte that renders as � and can make json_encode() fail outright. Any time the input can contain non-ASCII text, this is the function you want.
When to reach for it
Use it to build teasers and excerpts, to mask all but the last few digits of an account number, to take a fixed-length prefix for a slug, or to peel a known number of characters off the front of a parsed token. Negative start counts from the end, which is the neat way to grab a suffix: mb_substr($s, -4) is the last four characters.
What trips people up
A character is not a grapheme. Combining accents, flag emoji and skin-tone sequences are several Unicode characters that display as one, so mb_substr() can still split a visible glyph in half. If your text has those, use grapheme_substr() from the intl extension instead.
An out-of-range start returns an empty string rather than false. And do not confuse it with mb_strcut(), which takes byte offsets but rounds them to character boundaries — that one is for hitting a byte budget, not a character count.
Example
When to use it
- Build a 120-character teaser from a comment without producing broken characters.
- Mask a card or IBAN so only the last four characters are visible.
- Strip a known prefix such as 'Bearer ' from an authorization header value.
- Take the first character of a name to render an avatar initial.
Frequently asked questions
What is the difference between substr and mb_substr?
substr counts bytes and mb_substr counts characters. On UTF-8 input substr can cut in the middle of a multibyte sequence and produce an invalid string, while mb_substr always cuts on a character boundary.
How do I get the last N characters of a string in PHP?
Pass a negative start: mb_substr($string, -3) returns the last three characters. Combine it with a length, as in mb_substr($string, -5, 2), to take a window measured from the end.
What does mb_substr return if the start is past the end of the string?
An empty string. It never returns false, so check the result with === '' rather than a false comparison.
More examples
Teasers and masking
The teaser is exactly 20 characters plus the ellipsis, so mb_strlen reports 21 — with substr() the umlauts would have made the cut land mid-character. The masking line uses a negative start to take the final four characters without knowing the total length.
<?php
$comment = "Ich mag Bücher über Bäume und Blüten sehr gerne.";
$teaser = mb_substr($comment, 0, 20);
if (mb_strlen($comment) > 20) {
$teaser .= "…";
}
echo $teaser, "\n";
echo mb_strlen($teaser), "\n";
$card = "4111111111111111";
echo str_repeat("*", 12) . mb_substr($card, -4), "\n";Slicing at a position found by mb_strpos
mb_strpos returns a character offset, which mb_substr consumes directly — mixing it with substr() would be off by one byte for every accented character before the delimiter. The var_dump makes that gap visible: 23 characters but 24 bytes.
<?php
$line = "clé=valeur avec accents";
$pos = mb_strpos($line, "=");
echo mb_substr($line, 0, $pos), "\n";
echo mb_substr($line, $pos + 1), "\n";
var_dump(mb_strlen($line), strlen($line));
Discussion