Slicing Strings

Ma Mariya Vinciya Updated 17 Oct 2026
10 min read ·Lesson 37 of 102
In this post

What Is String Slicing? In simple terms: Slicing means taking a piece out of a string — the first few characters, the last few, or a section in the middle. Technically: Slicing extracts a substring :

What Is String Slicing?

In simple terms: Slicing means taking a piece out of a string — the first few characters, the last few, or a section in the middle.

Technically: Slicing extracts a substring: a contiguous part of a string defined by a starting position and a length. PHP's main slicing function is substr() (byte-based), with mb_substr() as the character-based version for multibyte text. Other functions extract parts based on a delimiter instead of a position.

Why Slicing Is Needed

  • Showing a short preview of a long description
  • Extracting the year from a date string like 2026-10-08
  • Getting the domain from an email address
  • Reading fixed-width fields from file formats or log lines
  • Showing only the last four digits of an account number
  • Getting a file extension

How Positions Work

Positions in a string start at 0. Negative positions count backward from the end.

String:     H   e   l   l   o       W   o   r   l   d
Position:   0   1   2   3   4   5   6   7   8   9   10
Negative: -11 -10  -9  -8  -7  -6  -5  -4  -3  -2  -1

"Hello World" has 11 characters. The first is at position 0, the last at position 10 (or -1).


substr()

Syntax

substr(string $string, int $offset, ?int $length = null): string
ParameterMeaning
$stringThe source string
$offsetWhere to start. Positive: from the beginning. Negative: from the end.
$lengthOptional. Positive: how many characters to take. Negative: stop that many characters before the end. Omitted or null: take everything to the end.

Example

<?php
$text = "Hello World";

echo substr($text, 0, 5) . "\n";   // from position 0, take 5
echo substr($text, 6) . "\n";      // from position 6 to the end
echo substr($text, -5) . "\n";     // last 5 characters
echo substr($text, -5, 3) . "\n";  // start 5 from the end, take 3
echo substr($text, 0, -6) . "\n";  // from start, stop 6 before the end

Output

Hello
World
World
Wor
Hello

Step-by-Step Explanation

CallStartLength RuleResult
substr($text, 0, 5)H (0)Take 5Hello
substr($text, 6)W (6)To the endWorld
substr($text, -5)W (-5)To the endWorld
substr($text, -5, 3)W (-5)Take 3Wor
substr($text, 0, -6)H (0)Drop the last 6Hello

A negative length is useful when you know how much to remove from the end rather than how much to keep — for example, removing a fixed suffix.

Out-of-Range Values

<?php
var_dump(substr("Hello", 10));
var_dump(substr("Hello", 2, 100));
var_dump(substr("Hello", -100, 2));

Output

string(0) ""
string(3) "llo"
string(2) "He"
  • Starting beyond the end returns an empty string.
  • A length that goes past the end simply stops at the end.
  • A negative offset larger than the string starts at position 0.

Version note: Before PHP 8.0, substr() returned false when the offset was beyond the end. Since PHP 8.0, it always returns a string (empty when nothing is extracted). Old code that checks substr(...) === false no longer detects this case; check for "" instead.


Getting the First and Last Characters

<?php
$code = "INV2026";

echo substr($code, 0, 1) . "\n";   // first character
echo $code[0] . "\n";              // same, using offset syntax
echo substr($code, -1) . "\n";     // last character
echo $code[-1] . "\n";             // same, PHP 7.1+
echo substr($code, 0, 3) . "\n";   // prefix
echo substr($code, 3) . "\n";      // everything after the prefix

Output

I
I
6
6
INV
2026

$code[0] and $code[-1] are convenient for single characters, but they raise a warning if the string is empty or too short. substr() returns an empty string instead, which is safer when the input is not guaranteed.


Practical Use: Fixed-Position Data

Some formats place fields at fixed positions. Slicing reads them directly.

<?php
$filename = "invoice_2026-10-08_1042.pdf";

$date = substr($filename, 8, 10);
$invoiceNo = substr($filename, 19, 4);
$year = substr($date, 0, 4);

echo "Date: $date\n";
echo "Invoice: $invoiceNo\n";
echo "Year: $year\n";

Output

Date: 2026-10-08
Invoice: 1042
Year: 2026

How It Works

  • invoice_ is 8 characters long, so the date starts at position 8 and has 10 characters.
  • After the date comes _ (position 18), so the invoice number starts at position 19.

Fixed positions are fragile: if the format changes (for example, a different prefix), every offset becomes wrong. When the data has delimiters, splitting by the delimiter is more robust:

<?php
[$type, $date, $rest] = explode("_", "invoice_2026-10-08_1042.pdf");
$invoiceNo = pathinfo($rest, PATHINFO_FILENAME);

echo "$type | $date | $invoiceNo";

Output

invoice | 2026-10-08 | 1042

The square brackets on the left destructure the array returned by explode() into three variables (see Array Destructuring). pathinfo(..., PATHINFO_FILENAME) returns the name without its extension.


Slicing by Delimiter: strstr() and strrchr()

When you do not know the position but do know a character that separates the parts, use functions that slice around that character.

strstr() — From the First Occurrence

strstr(string $haystack, string $needle, bool $before_needle = false): string|false
<?php
$email = "customer.support@gocourse.in";

echo strstr($email, "@") . "\n";
echo strstr($email, "@", true) . "\n";
echo substr(strstr($email, "@"), 1) . "\n";

Output

@gocourse.in
customer.support
gocourse.in
  • strstr($email, "@") returns everything from the first @ to the end, including the @.
  • With true as the third argument, it returns everything before the @.
  • substr(..., 1) removes the leading @ to get just the domain.

strstr() returns false if the needle is not found. Check for that before using the result, or validate the input first.

strrchr() — From the Last Occurrence

<?php
$file = "report.final.v2.pdf";

echo strrchr($file, ".") . "\n";
echo pathinfo($file, PATHINFO_EXTENSION) . "\n";

Output

.pdf
pdf

strrchr() finds the last occurrence of a character and returns everything from there. Because file names can contain several dots, the last one is the one that matters for the extension. For file paths specifically, pathinfo() is clearer: it returns the extension without the dot and also handles directory names.

Extracting Text Between Two Markers

Combining strpos() with substr() extracts a section between two delimiters:

<?php
function between(string $text, string $start, string $end): ?string
{
    $startPos = strpos($text, $start);

    if ($startPos === false) {
        return null;
    }

    $startPos += strlen($start);
    $endPos = strpos($text, $end, $startPos);

    if ($endPos === false) {
        return null;
    }

    return substr($text, $startPos, $endPos - $startPos);
}

var_dump(between("Order ID: [A-1042] confirmed", "[", "]"));
var_dump(between("No brackets here", "[", "]"));

Output

string(6) "A-1042"
NULL

Step-by-Step Explanation

  1. strpos($text, $start) finds the position of [ (position 10).
  2. Adding strlen($start) moves past the marker, so extraction starts at A (position 11).
  3. strpos($text, $end, $startPos) searches for ] starting after the opening marker, so an earlier ] cannot be matched by mistake.
  4. The length is the distance between the two positions: $endPos - $startPos.
  5. If either marker is missing, the function returns null instead of a wrong slice. Note the strict === false checks — a marker at position 0 is valid.

For more complex extraction, regular expressions with preg_match() are often simpler (see preg_match).


Slicing Multibyte Text: mb_substr()

The Problem

substr() counts bytes. In UTF-8, characters such as é, ₹, and Tamil letters use multiple bytes. Cutting through a character produces invalid text.

<?php
$drink = "Café Mocha";

var_dump(substr($drink, 0, 4));
var_dump(mb_substr($drink, 0, 4));

Output

string(4) "Caf�"
string(5) "Café"

Why

é is stored as two bytes. substr($drink, 0, 4) takes C, a, f, and only the first byte of é. That lone byte is not a valid character, so it is displayed as � (the replacement character). mb_substr() counts characters, so it returns all of é — 4 characters, 5 bytes.

Syntax

mb_substr(string $string, int $start, ?int $length = null, ?string $encoding = null): string

The parameters work like substr(), including negative values, but positions are in characters. The encoding defaults to UTF-8 (the mbstring default).

Rule

Use mb_substr() for any text that a person writes or reads: names, titles, messages, comments, and descriptions. Use substr() for data that is guaranteed to be ASCII, such as dates in YYYY-MM-DD format, hexadecimal hashes, or fixed technical codes.


Truncating Text for Display: mb_strimwidth()

mb_strimwidth() shortens text to a maximum display width and adds a marker such as ...:

<?php
$title = "Learning PHP step by step";

echo mb_strimwidth($title, 0, 15, "...") . "\n";
echo mb_strimwidth("Short", 0, 15, "...") . "\n";

Output

Learning PHP...
Short

How It Works

  • The width (15) includes the trim marker. The result is 12 characters of text plus ....
  • If the text already fits, it is returned unchanged, without a marker.
  • It measures display width: East Asian full-width characters count as 2, which keeps columns aligned in fixed-width output.

mb_strimwidth() may cut in the middle of a word. For word-aware truncation, cut with mb_substr() and then shorten to the last space, as shown in the excerpt() example in String Functions.


Splitting Into Equal Pieces: str_split()

<?php
$card = "4111111111111111";

echo implode(" ", str_split($card, 4)) . "\n";

$hex = "ff8800";
[$r, $g, $b] = array_map('hexdec', str_split($hex, 2));
echo "R: $r, G: $g, B: $b\n";

Output

4111 1111 1111 1111
R: 255, G: 136, B: 0
  • str_split($card, 4) divides the string into 4-character chunks, which are joined with spaces — the familiar card number format.
  • For the color, the hex string is split into 2-character chunks (ff, 88, 00), and hexdec() converts each to a decimal number.

Use mb_str_split() for multibyte text.


Splitting at the First Delimiter: explode() With a Limit

When only the first occurrence of a delimiter matters, explode() with a limit of 2 splits the string into exactly two parts:

<?php
$line = "token=abc=123";

[$key, $value] = explode("=", $line, 2);

echo "Key: $key\n";
echo "Value: $value\n";

Output

Key: token
Value: abc=123

Without the limit, the value would be split again at its own = sign, and abc=123 would be broken into two parts. This pattern is used for parsing configuration lines, HTTP headers (Name: value), and query-style data.


Choosing the Right Tool

You KnowUse
Exact position and length (ASCII data)substr()
Exact position and length (human text)mb_substr()
A delimiter; you want the part after or before its first occurrencestrstr() (with true for before)
A delimiter; you want the part after its last occurrencestrrchr()
Start and end markersstrpos() + substr(), or preg_match()
A delimiter separating several fieldsexplode()
A maximum display lengthmb_strimwidth()
A fixed chunk sizestr_split() / mb_str_split()
A file pathpathinfo(), basename(), dirname()

Common Mistakes

Using substr() on Multibyte Text

Effect: Broken characters (�) and invalid UTF-8, which can also break JSON encoding.

Fix: Use mb_substr() for human-readable text.

Confusing Length With End Position

<?php
// Intention: characters from position 6 to position 8
echo substr("Hello World", 6, 8);

Effect: Prints World — the third argument is a length (8 characters), not an end position, so it takes everything available.

Fix: Calculate the length: substr($s, 6, 8 - 6 + 1) for positions 6 through 8 inclusive (Wor).

Not Handling a Missing Delimiter

Effect: strstr() or strpos() returns false, and passing false to substr() produces wrong results or errors.

Fix: Check with === false before slicing, or use str_contains() first.

Checking substr() for false in PHP 8

Effect: The check never triggers, because PHP 8 returns "".

Fix: Check for an empty string or validate the length beforehand.

Using rtrim() to Remove a Suffix

Effect: rtrim("lessons.json", ".json") returns "le", not "lessons". The second argument is a list of characters, so PHP keeps removing any trailing ., j, s, o, or n — including the ones that belong to the word itself.

Fix: str_ends_with($s, $suffix) ? substr($s, 0, -strlen($suffix)) : $s.


Best Practices

  • Use mb_substr() and mb_strimwidth() for user-facing text; use substr() for ASCII data.
  • Prefer delimiter-based extraction (explode(), strstr(), pathinfo()) over hard-coded positions when the format has separators.
  • Check search results with === false before using them as positions.
  • Use explode() with a limit when only the first delimiter should split the string.
  • Keep slicing logic in small, named functions (between(), excerpt()) so it can be tested and reused.

Official reference: https://www.php.net/manual/en/function.substr.php

0 Comments

Reviewed before they appear

No comments yet.

PHP
PHP Home PHP Switch PHP Match Alternative Syntax PHP Loops While Loop Do While Loop For Loop Foreach Loop Nested Loops Break Statement PHP Home PHP Intro PHP Install PHP Built-in Web Server PHP Syntax PHP Tags PHP and HTML PHP Comments PHP Multiline Comments PHP Variables Variables Scope PHP Casting Logical Operators Null Coalescing Operator Spaceship Operator Operator Precedence Continue Statement Modify Strings PHP If...Else...Elseif PHP If Modify Strings PHP If Operators PHP Functions Concatenate Strings PHP If...Else Slicing Strings User-defined Functions Slicing Strings PHP Shorthand if Escape Characters Default Argument Values PHP Nested if Pass by Reference String Interpolation Named Arguments PHP File Upload Variadic Functions Return Values Search in Strings Multibyte Strings Type Declarations in Functions PHP Numbers PHP Math PHP Floats PHP Random Numbers PHP Constants PHP Properties PHP Methods PHP Constructor Constructor Property Promotion PHP Cookies PHP Destructor PHP Access Modifiers Readonly Properties PHP Sessions PHP Inheritance Method Overriding PHP Filters The final Keyword CSRF Protection PHP Filters Advanced Session Security Secure Cookies PHP Magic Constants File Upload Security PHP Callback Functions PHP Operators Filesystem Security PHP JSON Arithmetic Operators Error Reporting in Production Hiding PHP Assignment Operators PHP Configuration (php.ini) Comparison Operators PHP Command Line (CLI) PHP Exceptions Increment / Decrement Operators Migrating to PHP 8 PHP Form Validation PHP Form Required PHP Form URL/E-mail Error Handling Form Input Sanitizing Escaping Output (htmlspecialchars) Custom Error Handler Checkbox, Radio and Select Inputs Sticky Forms PHP Form Complete PHP Date and Time Date Formatting
Ask about this post
AI Ask about this post

Ask questions about Slicing Strings and get answers drawn from it.

Signed-in readers only.