Str::substr
Returns a portion of a string by character offset, safely handling multibyte text.
Str::substr(string $string, int $start, ?int $length = null, string $encoding = 'UTF-8'): string- Added in Laravel 5.2
Parameters
| Parameter | Type | Description |
|---|---|---|
$$string | string | The string to slice. |
$$start | int | Zero-based character offset to start at. A negative value counts back from the end of the string. |
$$lengthoptional | int|null= null | Number of characters to return. null runs to the end of the string; a negative value stops that many characters before the end. |
$$encodingoptional | string= 'UTF-8' | Character encoding passed through to mb_substr. |
Return value
stringThe requested portion of the string, or an empty string when the range falls outside it.
Changelog
- PHP 8.xPHP 8 made mb_substr return an empty string instead of false for an out-of-range start, so this method no longer returns false under any input.
Str::substr is a thin wrapper over mb_substr with UTF-8 as the default encoding. That single default is the reason to use it: PHP's plain substr counts bytes, so slicing accented or CJK text at an arbitrary offset splits a character in half and produces mojibake or invalid UTF-8 that a JSON encode will later reject.
Reach for it whenever the offsets are meaningful to a human — the last four digits of a card number, a fixed-width column from a legacy export, an initial for an avatar, a prefix you already located with Str::startsWith.
What trips people up
- Offsets are characters, not bytes, and not grapheme clusters either. An emoji with a skin-tone modifier or a combining accent still counts as more than one character, so a naive slice can separate a base letter from its accent.
- A negative
$lengthis an end offset, not a count:Str::substr('abcdef', 1, -2)isbcd. - An out-of-range start returns
'', neverfalse, so an=== falsecheck will never fire. - For truncating display text,
Str::limitandStr::wordsare almost always the better tool because they add an ellipsis and avoid cutting mid-word.
Example
When to use it
- Show only the last four digits of a card or account number in a receipt.
- Pull fixed-width fields out of a legacy flat-file export.
- Take the first character of a name for an avatar placeholder without corrupting non-Latin scripts.
Frequently asked questions
What is the difference between Str::substr and PHP's substr?
Str::substr counts characters using mb_substr with UTF-8 by default, while substr counts bytes. On any text containing accents, CJK characters or emoji, substr can split a character in half and produce invalid UTF-8.
Does Str::substr ever return false?
No. An out-of-range start produces an empty string, so checking the result with === false will never be true; test for an empty string instead.
How do I get the last N characters of a string?
Pass a negative start with no length: Str::substr($value, -4) returns the final four characters. Use Str::mask instead if the goal is to hide the rest rather than extract the tail.
More examples
Masking and excerpting
The first two lines both print ************1234, showing that Str::mask is the clearer choice when the goal is redaction rather than extraction. The dump is "日本語" — three characters, nine bytes — and Str::length reports 7 for the full string, confirming that both helpers count characters while strlen() would report 21.
<?php
use Illuminate\Support\Str;
$card = '4111111111111234';
echo str_repeat('*', 12).Str::substr($card, -4), PHP_EOL;
// Str::mask does the same job in one call.
echo Str::mask($card, '*', 0, 12), PHP_EOL;
// Slicing non-Latin text stays valid.
var_dump(Str::substr('日本語テキスト', 0, 3));
var_dump(Str::length('日本語テキスト'));Parsing a fixed-width record
Prints id 123, name "José Álvarez" and amount 425.5. Using Str::substr rather than substr is essential here: the accented characters make the byte offsets and character offsets diverge, so plain substr would return a truncated name and shift the amount field. Str::squish cleans the padding spaces from the fixed-width column.
<?php
use Illuminate\Support\Str;
// cols 0-5 id, 6-25 name, 26-33 amount
$line = '000123José Álvarez 00042550';
$record = [
'id' => (int) Str::substr($line, 0, 6),
'name' => Str::squish(Str::substr($line, 6, 20)),
'amount' => (int) Str::substr($line, 26, 8) / 100,
];
print_r($record);
Discussion