Strings & String Functions

Reviewed & published by Brayan K

Text is the lifeblood of web apps — names, emails, URLs, messages. This lesson covers building strings, searching them, slicing them, splitting and joining, formatting with sprintf, and the multibyte functions you need for real-world Unicode text.

Part of the free PHP course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn in This Lesson

1️⃣ Building Strings

There are four ways to assemble text, and you'll mix them constantly. The dot operator glues strings together. Double quotes interpolate variables. Curly braces like {$var} remove any ambiguity about where a variable name ends. And sprintf() builds precisely formatted output from placeholders — ideal for money and padded numbers.

<?php
$first = "Grace";
$last  = "Hopper";

// 1) Concatenation with the dot operator.
$full = $first . " " . $last;
echo $full . "\n";                  // Grace Hopper

// 2) Interpolation inside double quotes.
echo "Welcome, $first!\n";          // Welcome, Grace!

// 3) Curly braces remove ambiguity around a variable.
$role = "admin";
echo "Role: {$role}istrator\n";     // Role: administrator

// 4) sprintf() builds formatted strings with placeholders.
$price = 4.5;
echo sprintf("Total: $%0.2f\n", $price);   // Total: $4.50
?>

The %0.2f in sprintf means "a float with exactly two decimal places", which is why 4.5 prints as 4.50 — perfect for currency.

2️⃣ Length, Case & Trimming

The day-to-day toolkit: strlen() for length, strtoupper()/strtolower() for case, ucfirst()/ucwords() for capitalisation, and trim() to strip stray whitespace — which you should run on essentially every piece of user input.

<?php
$text = "Hello, PHP";

echo strlen($text) . "\n";          // 10  — number of BYTES
echo strtoupper($text) . "\n";      // HELLO, PHP
echo strtolower($text) . "\n";      // hello, php
echo ucfirst("hello world") . "\n"; // Hello world
echo ucwords("hello world") . "\n"; // Hello World

// trim() removes whitespace from both ends — vital for user input.
$padded = "   spaced out   ";
echo "[" . trim($padded) . "]\n";   // [spaced out]
?>

3️⃣ Searching Within Strings

To ask "does this contain…?", PHP 8 gives you three readable booleans: str_contains(), str_starts_with(), and str_ends_with(). When you need the actual position, strpos() returns an index — but watch the classic trap below.

<?php
$email = "grace@navy.mil";

// str_contains / str_starts_with / str_ends_with — PHP 8, readable booleans.
var_dump(str_contains($email, "@"));        // bool(true)
var_dump(str_starts_with($email, "grace")); // bool(true)
var_dump(str_ends_with($email, ".com"));    // bool(false)

// strpos() returns the index of the first match, or false if absent.
$at = strpos($email, "@");
echo "@ is at index $at\n";                 // @ is at index 5

// ALWAYS check with === false: index 0 is falsy but valid!
if (strpos($email, "g") === 0) {
    echo "Starts with g\n";                 // Starts with g
}
?>

The trap: if a match is at the very start, strpos returns 0, which is falsy. Always compare with === false to tell "not found" apart from "found at index 0".

4️⃣ Slicing, Replacing, Splitting

substr() extracts a slice (negative offsets count from the end). str_replace() swaps every occurrence. And the explode()/implode() pair converts between a delimited string and an array — the bread and butter of parsing CSV rows, paths, and tags.

<?php
$code = "ORD-2026-USA";

// substr(string, start, length) — extract a slice.
echo substr($code, 0, 3) . "\n";    // ORD
echo substr($code, 4, 4) . "\n";    // 2026
echo substr($code, -3) . "\n";      // USA  (negative start = from the end)

// str_replace swaps every occurrence.
echo str_replace("-", "/", $code) . "\n";   // ORD/2026/USA

// explode() splits into an array; implode() joins one back.
$parts = explode("-", $code);
echo $parts[1] . "\n";                       // 2026
echo implode(" | ", $parts) . "\n";          // ORD | 2026 | USA
?>

5️⃣ Unicode: The mb_* Functions

Here's the gotcha that bites every PHP developer eventually: the plain string functions count bytes, not characters. Real text — names with accents, emoji, non-Latin scripts — is UTF-8, where one character can be several bytes. For correctness, use the multibyte equivalents: mb_strlen, mb_substr, mb_strtoupper.

<?php
// Plain strlen counts BYTES. Multibyte (UTF-8) letters take 2-4 bytes,
// so for anything with accents or emoji, use the mb_* functions.
$word = "café";              // the é is two bytes in UTF-8

echo strlen($word) . "\n";      // 5  — bytes, NOT characters!
echo mb_strlen($word) . "\n";   // 4  — actual character count

echo mb_strtoupper("münchen") . "\n";  // MÜNCHEN  (handles ü correctly)
echo mb_substr("café", 0, 3) . "\n";   // caf
?>

Now you try. Fill the ___ blanks to clean up and reformat a username.

<?php
// 🎯 YOUR TURN — fill each ___ to clean up and format a username.
$raw = "   Ada_Lovelace   ";

$clean = ___($raw);                  // 👉 trim() the surrounding spaces
$lower = ___($clean);                // 👉 strtolower() it
$pretty = ___($lower, "_", " ");     // 👉 str_replace _ with a space

echo "[" . $clean . "]\n";          // [Ada_Lovelace]
echo $lower . "\n";                 // ada_lovelace
echo $pretty . "\n";                // ada lovelace

// ✅ Expected output:
//    [Ada_Lovelace]
//    ada_lovelace
//    ada lovelace
?>

🧩 Reorder Challenge

These scrambled lines should take a full name and print initials. Put them in the order that produces G.H.

You must define $name first, then split it into $parts with explode, then build $initials from those parts (a string indexed with [0] gives its first character), and only then echo. Output: G.H.

🧠 Quick Recall

Predict the output before revealing each answer.

Prints 5. The é is two bytes in UTF-8, and strlen counts bytes. Use mb_strlen to get the character count 4.

Prints ef. A negative start counts from the end of the string, so -2 means "the last two characters".

Prints bool(false). The match is at index 0, so strpos returns 0 — which is not identical to false. This is exactly why you compare with === false.

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — String Functions

FunctionExampleResult
strlenstrlen("hi")2 (bytes)
strtoupperstrtoupper("hi")HI
trimtrim(" x ")x
str_containsstr_contains("abc","b")true
substrsubstr("abc",0,2)ab
str_replacestr_replace("a","x","aa")xx
explodeexplode(",","a,b")["a","b"]
sprintfsprintf("%0.2f",4.5)4.50

Mini-Challenge: Build a URL Slug

Write it yourself, run it on onecompiler.com/php or your own machine, then check against the expected output in the comments.

<?php
// 🎯 MINI-CHALLENGE: turn a raw title into a URL "slug".
// No code filled in — follow the steps, then run and compare.
//
// 1. Start with $title = "  Learn PHP 8 FAST!  ".
// 2. trim() the spaces.
// 3. strtolower() it.
// 4. str_replace the spaces with hyphens "-".
// 5. str_replace "!" with "" (remove it).
// 6. echo the result.
//
// ✅ Expected output:
//    learn-php-8-fast

// your code here
?>

🎉 Lesson Complete!

Practice quiz

What does strlen() actually count?

  • Characters
  • Words
  • Bytes
  • Lines

Answer: Bytes. strlen counts bytes, not characters — multibyte (UTF-8) letters take 2-4 bytes each.

What does echo strlen("café"); print?

  • 3
  • 4
  • 5
  • 6

Answer: 5. The é is two bytes in UTF-8, so strlen returns 5. mb_strlen would return 4.

Which function correctly counts the number of characters in Unicode text?

  • strlen
  • mb_strlen
  • count
  • strpos

Answer: mb_strlen. mb_strlen (and the mb_* family) handle multibyte characters correctly.

What does echo substr("ORD-2026-USA", -3); print?

  • ORD
  • USA
  • 2026
  • -US

Answer: USA. A negative start counts from the end, so -3 returns the last three characters: USA.

Why must you compare strpos() with === false?

  • strpos is very slow
  • A match at index 0 returns 0, which is falsy
  • strpos returns a string
  • It only works on numbers

Answer: A match at index 0 returns 0, which is falsy. A match at the start gives index 0; 0 is falsy, so === false tells 'not found' apart from 'found at 0'.

Which PHP 8 function returns a clean boolean for 'does it contain X?'

  • strpos
  • str_contains
  • substr
  • explode

Answer: str_contains. str_contains returns a boolean and reads like plain English.

What does explode("-", "ORD-2026-USA") return?

  • The string "ORD2026USA"
  • An array: ["ORD", "2026", "USA"]
  • The number 3
  • "ORD"

Answer: An array: ["ORD", "2026", "USA"]. explode splits a string on the separator into an array.

Which function joins an array back into a single string?

  • implode
  • explode
  • str_split
  • trim

Answer: implode. implode(glue, array) joins array elements into one string.

What does trim(" spaced out ") return?

  • "spacedout"
  • "spaced out" (no surrounding spaces)
  • An error
  • The original with spaces

Answer: "spaced out" (no surrounding spaces). trim removes whitespace from both ends, leaving "spaced out".

What does sprintf("%0.2f", 4.5) produce?

  • "4"
  • "4.5"
  • "4.50"
  • "450"

Answer: "4.50". %0.2f formats a float with exactly two decimal places, giving "4.50".

Continue this course

Frequently asked questions

What is the difference between single and double quotes for strings?

Double quotes interpolate: PHP replaces any $variable inside and processes escape sequences like and . Single quotes are literal: you get the exact characters typed, so '$name ' stays as those raw characters. Single quotes are also marginally faster because PHP doesn't scan them, but the real decision is whether you want interpolation. For multi-line text, heredoc behaves like double quotes and nowdoc like single quotes.

Why does strlen() give the wrong length for accented characters?

strlen() counts bytes, not characters. In UTF-8, characters like é, ü, or emoji take two to four bytes each, so strlen("café") returns 5 even though there are 4 characters. Use mb_strlen() — and the rest of the mb_* family like mb_substr() and mb_strtoupper() — whenever your text might contain non-ASCII characters, which in practice means almost always.

Should I use strpos() or str_contains() to find a substring?

Since PHP 8, prefer str_contains(), str_starts_with(), and str_ends_with() — they return a clean boolean and read like plain English. strpos() returns the integer index of the first match or false if not found, which is useful when you need the position, but it has a famous trap: a match at index 0 is the integer 0, which is falsy, so you must compare with === false rather than a plain truthiness check.

How do I split a string into parts and join them back?

Use explode(separator, string) to split a string into an array — explode(",", "a,b,c") gives ["a","b","c"]. Use implode(glue, array) to join an array back into a string — implode("-", ["a","b"]) gives "a-b". For fixed-position extraction use substr(), and for find-and-replace use str_replace(), which swaps every occurrence by default.