Signature
mb_strcut(string $string, int $start, ?int $length = null, ?string $encoding = null): stringPurpose
mb_strcut() extracts part of a string using byte offsets and a byte length, but it never leaves a half-written multibyte character at the edges. If a boundary falls in the middle of a character, the cut is moved back to the first byte of that character. This is the main difference from substr(), which cuts at the exact byte and can produce malformed text. It also differs from mb_substr(), which counts characters rather than bytes.
The function is most useful when you have a hard byte budget, such as a fixed-width storage column, a protocol field or a header limit, and you still want valid text.
Signature
Parameters and defaults
$string: the string to cut.$start: a byte position counted from zero. A negative value counts back from the end of the string. If its magnitude is larger than the string length, the result starts at the beginning.$length: the length in bytes. If it is omitted ornull, everything to the end is returned. A negative length ends the result that many bytes before the end of the string. If a negative length reaches back past the start position, the result is an empty string.$encoding: the character encoding. If it is omitted ornull, the internal encoding is used. Passing the encoding explicitly, such as'UTF-8', makes the code independent of that setting.
Return value and failure cases
The function returns a string: the selected portion of the input. The result can be shorter than you asked for, and it can be empty when the first character at the cut point does not fit in the byte range.
Since PHP 8.0, an invalid $encoding name throws a ValueError. Before PHP 8.0 this produced a warning. The PHP 8.4.0 changelog notes that behavior is more consistent for invalid UTF-8 and UTF-16 input, so do not rely on particular output for malformed strings. Validate untrusted input first, for example with mb_check_encoding().
Example 1: boundaries move back to a character start
In 'aé€b' the letter a uses one byte, é uses two, € uses three and b uses one, for seven bytes in total.
$text = 'aé€b';
echo json_encode([
'bytes' => strlen($text),
'three' => mb_strcut($text, 0, 3, 'UTF-8'),
'five' => mb_strcut($text, 0, 5, 'UTF-8'),
'six' => mb_strcut($text, 0, 6, 'UTF-8'),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);{"bytes":7,"three":"aé","five":"aé","six":"aé€"}A limit of 5 bytes would end inside the three-byte €, so the euro sign is left out. With 6 bytes it fits.
Example 2: comparing substr() and mb_strcut()
$text = '€uro';
$plain = substr($text, 0, 2);
$safe = mb_strcut($text, 0, 2, 'UTF-8');
echo json_encode([
'substrHex' => bin2hex($plain),
'substrValid' => mb_check_encoding($plain, 'UTF-8'),
'strcutHex' => bin2hex($safe),
'strcutValid' => mb_check_encoding($safe, 'UTF-8'),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);{"substrHex":"e282","substrValid":false,"strcutHex":"","strcutValid":true}substr() returns the first two bytes of the euro sign, which is not valid UTF-8. mb_strcut() returns an empty string, which is valid.
Example 3: fitting text into a byte limit
function fitBytes(string $text, int $maxBytes): string
{
return mb_strcut($text, 0, $maxBytes, 'UTF-8');
}
$results = [];
foreach ([1, 2, 6, 7] as $limit) {
$results[] = fitBytes('ñandú', $limit);
}
echo json_encode($results, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);["","ñ","ñand","ñandú"]
The word ñandú takes seven bytes because ñ and ú use two bytes each. A limit of 1 cannot hold ñ, so the result is empty. A limit of 6 cannot hold the final ú, so the result stops after ñand.
Edge cases
- The result may contain fewer bytes than
$length. Never assumestrlen()of the result equals the requested length. - Because the unit is bytes, a given
$lengthcan hold different numbers of characters depending on the text. - A start offset that lands inside a character is moved back to that character's first byte.
- With a single-byte encoding or plain ASCII text, every byte is a whole character, so the function behaves like
substr(). - A start offset beyond the end of the string gives an empty string.
Common mistakes
- Expecting character counts. If you want to count characters, use
mb_substr().mb_strcut()is for byte budgets. - Using
substr()for limits on UTF-8 text. This can split a character and produce invalid output that later breaks JSON encoding or display. - Forgetting the encoding. If the internal encoding differs from your data, boundaries are computed wrongly. Pass
'UTF-8'explicitly when you know the encoding. - Assuming the result is padded or exact. If you need a fixed-width field, pad the result yourself afterwards.
- Passing untrusted, unvalidated bytes. Output for malformed input is not something to depend on, so check it first.
Related functions
mb_substr()extracts by character count.mb_strlen()counts characters, whilestrlen()counts bytes.mb_strimwidth()truncates by display width.mb_internal_encoding()gets or sets the default encoding.mb_check_encoding()validates a string for an encoding.
Recap
mb_strcut() takes byte-based $start and $length values and moves any cut that falls inside a multibyte character back to the character's first byte. It returns a valid but possibly shorter string, throws ValueError for an unknown encoding, and is the right choice when a byte limit must be respected without corrupting text.
Tested examples
$text = 'aé€b';
echo json_encode([
'bytes' => strlen($text),
'three' => mb_strcut($text, 0, 3, 'UTF-8'),
'five' => mb_strcut($text, 0, 5, 'UTF-8'),
'six' => mb_strcut($text, 0, 6, 'UTF-8'),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);{"bytes":7,"three":"aé","five":"aé","six":"aé€"}
$text = '€uro';
$plain = substr($text, 0, 2);
$safe = mb_strcut($text, 0, 2, 'UTF-8');
echo json_encode([
'substrHex' => bin2hex($plain),
'substrValid' => mb_check_encoding($plain, 'UTF-8'),
'strcutHex' => bin2hex($safe),
'strcutValid' => mb_check_encoding($safe, 'UTF-8'),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);{"substrHex":"e282","substrValid":false,"strcutHex":"","strcutValid":true}
function fitBytes(string $text, int $maxBytes): string
{
return mb_strcut($text, 0, $maxBytes, 'UTF-8');
}
$results = [];
foreach ([1, 2, 6, 7] as $limit) {
$results[] = fitBytes('ñandú', $limit);
}
echo json_encode($results, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);["","ñ","ñand","ñandú"]