substr
Returns a portion of a string starting at a given offset, optionally limited to a length.
substr(string $string, int $offset, ?int $length = null): string- Added in PHP 4.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$string | string | The input string. |
$offset | int | Where the returned substring starts. Non-negative offsets count from the beginning; negative offsets count back from the end of the string. |
$lengthoptional | ?int= null | Maximum number of bytes to return. Negative values stop that many bytes before the end; 0 returns an empty string; null runs to the end of the string. |
Return value
stringThe extracted part of the string, or an empty string if nothing matches the requested range.
Changelog
- PHP 8.0.0length is nullable now. Passing null explicitly returns the substring up to the end of the string, where it previously returned an empty string.
- PHP 8.0.0The function now returns an empty string where it previously returned false (for example when offset is past the end of the string).
substr slices a string by byte offset. Positive offsets count from the start, negative offsets count from the end, and the length may be positive (how much to take), negative (how much to leave off the end) or null (take everything remaining).
When to reach for it
Use it whenever you already know the position: taking a fixed-width field out of a record, reading a prefix code, chopping a known extension off a filename, or pairing it with strpos to extract the text between two markers. The negative-length form is particularly handy — substr($s, 0, -4) drops the last four bytes without you needing to compute the length.
What trips people up
It counts bytes, not characters. Cutting a UTF-8 string at an arbitrary offset can land in the middle of a multibyte sequence and produce a broken character, which is why truncating user text should use mb_substr.
The other change to remember is that since PHP 8.0 an out-of-range request returns "" rather than false. Old code that checked if (substr(...) === false) no longer detects anything — check the length of the input instead. And do not use substr($s, 0, 1) === 'x' to test a prefix; str_starts_with says it more clearly.
Example
When to use it
- Pull a fixed-width field out of a legacy flat-file record.
- Drop a known suffix, such as substr($file, 0, -4) to remove '.csv'.
- Take the first N characters of a description for a list preview.
- Extract the text between two markers found with strpos.
Frequently asked questions
Why does substr return an empty string instead of false?
Since PHP 8.0 substr always returns a string; an offset past the end of the input yields "" rather than false. Code that tested for false should check the input length instead.
What is the difference between substr and mb_substr?
substr counts bytes and mb_substr counts characters in a given encoding. For UTF-8 text with accented or non-Latin characters, only mb_substr is guaranteed not to split a character.
How do I get the last N characters of a string?
Pass a negative offset: substr($s, -3) returns the last three bytes. Use mb_substr($s, -3) if the string may contain multibyte characters.
More examples
Extracting text between two markers
Prints 'Release notes'. The length argument is the distance between the two positions, which is the usual way substr and strpos are combined. In real code you would guard against strpos returning false first.
<?php
$html = '<title>Release notes</title>';
$start = strpos($html, '<title>') + 7;
$end = strpos($html, '</title>');
echo substr($html, $start, $end - $start), "\n";Byte slicing versus character slicing
The strlen/mb_strlen line prints '12 bytes, 10 chars', which is why the two slices differ: substr took 4 bytes and landed inside the two-byte ï, while mb_substr took 4 whole characters.
<?php
$text = 'naïve café';
echo substr($text, 0, 4), "|\n"; // may cut the ï in half
echo mb_substr($text, 0, 4), "|\n"; // naïv
echo strlen($text), ' bytes, ', mb_strlen($text), " chars\n";Truncating with an ellipsis
Prints 'Short' unchanged and 'A considerably longe…' for the second. The length check first matters — without it, short strings would get a pointless ellipsis appended even though nothing was cut.
<?php
function preview(string $text, int $max = 20): string {
return strlen($text) <= $max ? $text : substr($text, 0, $max - 1) . '…';
}
echo preview('Short'), "\n";
echo preview('A considerably longer description here'), "\n";
Discussion