PHP Strings
What Is a String?
In simple terms: A string is a piece of text — a name, a sentence, an email address, an HTML snippet, or even an entire file's content.
Technically: In PHP, a string is an ordered sequence of bytes. PHP does not store an encoding with the string; it simply stores bytes. When text is saved as UTF-8 (the standard for the web), English letters take one byte each, while characters such as é, ₹, த, or emoji take two to four bytes.
Why Strings Matter
Almost everything a web application handles is text at some point:
- Form input (
$_POST), URL parameters ($_GET), and cookies always arrive as strings - HTML output sent to the browser
- JSON sent to and received from APIs
- SQL queries, file paths, email bodies, log messages
Understanding how strings are written, stored, and compared prevents a large class of bugs.
Creating Strings
PHP has four ways to write a string literal:
| Syntax | Name | Variables Interpolated? | Escape Sequences? |
|---|---|---|---|
'text' | Single-quoted | No | Only \' and 0 |
"text" | Double-quoted | Yes | Yes (\n, \t, \$, …) |
<<<ID ... ID | Heredoc | Yes | Yes |
<<<'ID' ... ID | Nowdoc | No | No |
This lesson focuses on single and double quotes. Heredoc and nowdoc are covered in Heredoc and Nowdoc, escape sequences in Escape Characters, and interpolation in String Interpolation.
Single-Quoted Strings
<?php
$language = 'PHP';
echo 'I am learning $language\n';
Output
I am learning $language\n
Single-quoted strings are literal. $language is not replaced, and \n is printed as a backslash followed by n. The only special sequences are \' (a single quote) and 0 (a backslash).
Double-Quoted Strings
<?php
$language = 'PHP';
echo "I am learning $language\n";
Output
I am learning PHP
Double-quoted strings replace variables with their values and interpret escape sequences such as \n (newline).
Which Quotes Should You Use?
- Use single quotes for fixed text with no variables: array keys (
$user['email']), SQL with placeholders, file paths, regular expression patterns. What you see is exactly what you get. - Use double quotes when you need variables or escape sequences inside the text.
The performance difference between the two is negligible in modern PHP. Choose based on clarity.
Quotes Inside Strings
<?php
echo 'It\'s a "great" day' . "\n";
echo "It's a \"great\" day" . "\n";
echo "It's a 'great' day" . "\n";
Output
It's a "great" day
It's a "great" day
It's a 'great' day
A quote of the same type as the delimiter must be escaped with a backslash. A quote of the other type needs no escaping. Choosing the right delimiter often avoids escaping altogether.
Multiline Strings
Strings can span several lines. The line breaks become part of the string.
<?php
$address = "Gocourse Learning Centre
12 Anna Salai
Chennai 600002";
echo $address;
Output
Gocourse Learning Centre
12 Anna Salai
Chennai 600002
In a browser, line breaks inside text are displayed as spaces. To show them as visible line breaks in HTML, use nl2br() (which inserts <br> tags) or wrap the text in a <pre> element.
String Length
strlen() Counts Bytes
<?php
echo strlen("Hello") . "\n";
echo strlen("Hello World") . "\n";
echo strlen("") . "\n";
Output
5
11
0
Spaces count as characters, and an empty string has length 0.
mb_strlen() Counts Characters
<?php
$price = "₹500";
$greeting = "Héllo";
echo strlen($price) . " bytes, " . mb_strlen($price) . " characters\n";
echo strlen($greeting) . " bytes, " . mb_strlen($greeting) . " characters\n";
Output
6 bytes, 4 characters
6 bytes, 5 characters
Why the Results Differ
In UTF-8, ₹ is stored as 3 bytes and é as 2 bytes. strlen() counts every byte; mb_strlen() decodes UTF-8 and counts actual characters.
| Function | Counts | Use For |
|---|---|---|
strlen() | Bytes | Binary data, storage limits measured in bytes, ASCII-only identifiers |
mb_strlen() | Characters | Anything a human reads: names, messages, form validation ("max 50 characters") |
For user-facing text, use the mb_* functions. They are covered in Multibyte Strings. Since PHP 8.0, the mbstring functions use UTF-8 by default unless configured otherwise.
Accessing Individual Characters
Strings can be accessed like arrays of bytes using square brackets and a zero-based position (offset).
Example
<?php
$word = "Developer";
echo $word[0] . "\n"; // first character
echo $word[4] . "\n"; // fifth character
echo $word[-1] . "\n"; // last character
echo $word[-3] . "\n"; // third from the end
Output
D
l
r
p
How It Works
- Offsets start at
0, so$word[0]isDand$word[4]isl(D-e-v-e-l). - Negative offsets (PHP 7.1+) count from the end:
-1is the last character.
Reading Beyond the End
<?php
$code = "ABC";
echo $code[10];
This raises a warning ("Uninitialized string offset") and returns an empty string. Check the length first, or use substr(), which handles out-of-range positions without warnings.
Modifying Individual Characters
<?php
$status = "cat";
$status[0] = "b";
echo $status;
Output
bat
PHP strings are mutable at the byte level: you can replace a single byte by assigning to its offset. If you assign beyond the end, PHP pads the gap with spaces.
Warning: Offsets work on bytes, not characters.
"₹500"[0]returns only the first of the three bytes of₹, which is not a valid character on its own. For multibyte text, usemb_substr($text, 0, 1).
Version note: The old syntax
$word{0}was removed in PHP 8.0. Always use square brackets.
Strings Are Copied, Functions Return New Strings
String functions do not change the original variable. They return a new string.
<?php
$name = " priya ";
trim($name);
echo "[$name]\n";
$name = trim($name);
echo "[$name]\n";
Output
[ priya ]
[priya]
The first trim() call returns a trimmed string, but the result is discarded because it is not assigned. To keep the change, assign the result back to a variable. This applies to almost all string functions: strtoupper(), str_replace(), substr(), and so on.
Comparing Strings
Equality
<?php
var_dump("apple" === "apple"); // bool(true)
var_dump("Apple" === "apple"); // bool(false) — case-sensitive
var_dump(strcasecmp("Apple", "apple") === 0); // bool(true) — case-insensitive
=== compares strings byte by byte, so uppercase and lowercase letters are different. strcasecmp() compares without case sensitivity and returns 0 when the strings are equal.
Numeric Strings and Loose Comparison
When both sides of == are numeric strings, PHP compares them as numbers:
<?php
var_dump("10" == "1e1"); // bool(true) — both are the number 10
var_dump("100" == "1e2"); // bool(true)
var_dump("007" == "7"); // bool(true)
var_dump("007" === "7"); // bool(false)
This can cause real bugs — for example, comparing tokens, codes, or hashes that happen to look numeric. Always compare strings with ===.
PHP 8 Change: Comparing Strings With Numbers
<?php
var_dump(0 == "abc"); // bool(false) in PHP 8; bool(true) in PHP 7
var_dump(0 == ""); // bool(false) in PHP 8; bool(true) in PHP 7
var_dump(42 == "42"); // bool(true) in both
In PHP 7, a non-numeric string was converted to a number (0) before comparison, so 0 == "abc" was true. Since PHP 8.0, when a number is compared with a non-numeric string, the number is converted to a string instead, and the comparison is false. This change removed a long-standing source of security bugs.
Ordering Strings
<?php
var_dump(strcmp("apple", "banana")); // negative: "apple" comes first
var_dump("apple" < "banana"); // bool(true)
$files = ["img12.png", "img10.png", "img2.png"];
sort($files);
print_r($files);
natsort($files);
print_r(array_values($files));
Output
int(-1)
bool(true)
Array
(
[0] => img10.png
[1] => img12.png
[2] => img2.png
)
Array
(
[0] => img2.png
[1] => img10.png
[2] => img12.png
)
strcmp()returns a negative number,0, or a positive number depending on order. Since PHP 8.2, it returns exactly-1,0, or1.- Standard sorting compares character by character, so
"img10"comes before"img2"(because1<2). natsort()uses natural order, which treats numbers inside strings as numbers — the order a human expects.
Converting Between Strings and Numbers
Strings to Numbers
<?php
var_dump((int) "42"); // int(42)
var_dump((float) "3.75"); // float(3.75)
var_dump((int) "12 apples"); // int(12) — leading number is used
var_dump((int) "apples 12"); // int(0) — no leading number
var_dump("5" + "10"); // int(15)
A string that starts with a number followed by other text ("12 apples") is called a leading-numeric string; casting it with (int) silently uses the leading number. Using such a string directly in arithmetic ("12 apples" + 1) also uses the leading number but raises the warning "A non-numeric value encountered". A completely non-numeric string in arithmetic ("apples" + 1) throws a TypeError in PHP 8. Conversion rules are covered in PHP Casting and PHP Type Juggling.
Validating Numeric Strings
<?php
var_dump(is_numeric("42")); // bool(true)
var_dump(is_numeric("4.2e1")); // bool(true)
var_dump(is_numeric(" 42")); // bool(true) — leading whitespace allowed
var_dump(is_numeric("42 ")); // bool(true) — trailing whitespace allowed since PHP 8.0
var_dump(is_numeric("42abc")); // bool(false)
Numbers to Strings
<?php
$quantity = 3;
$text = "Items: " . $quantity; // automatic conversion
$formatted = number_format(1234567.891, 2);
var_dump($text, $formatted);
Output
string(8) "Items: 3"
string(12) "1,234,567.89"
Practical Example: Validating and Normalizing a Username
<?php
function normalizeUsername(string $input): ?string
{
$username = strtolower(trim($input));
$length = mb_strlen($username);
if ($length < 3 || $length > 20) {
return null;
}
if (!ctype_alnum(str_replace('_', '', $username))) {
return null;
}
return $username;
}
$inputs = [" Kavin_R ", "ab", "hello world", "Dev_2026"];
foreach ($inputs as $input) {
$result = normalizeUsername($input);
printf("%-16s => %s\n", "'$input'", $result ?? 'invalid');
}
Output
' Kavin_R ' => kavin_r
'ab' => invalid
'hello world' => invalid
'Dev_2026' => dev_2026
Step-by-Step Explanation
trim()removes surrounding spaces, which users often type accidentally.strtolower()normalizes case so thatKavin_Randkavin_rcannot be registered as two different users.mb_strlen()counts characters for the length rule.str_replace('_', '', $username)temporarily removes underscores, thenctype_alnum()checks that every remaining character is a letter or digit. This rejects spaces and symbols while allowing underscores.- The function returns the cleaned username, or
nullwhen the input is invalid.
This pattern — trim, normalize, validate, then use — applies to emails, phone numbers, coupon codes, and most other text input.
Common Mistakes
Expecting Variables to Work in Single Quotes
Effect: 'Hello $name' prints $name literally.
Fix: Use double quotes or concatenation.
Using strlen() for User-Facing Length Limits
Effect: A 10-character name in Tamil or with accents may be rejected as "too long".
Fix: Use mb_strlen().
Forgetting to Assign Function Results
Effect: trim($name); has no effect on $name.
Fix: $name = trim($name);
Comparing Strings With ==
Effect: "1e3" == "1000" is true.
Fix: Use === for string comparisons.
Accessing Multibyte Characters by Offset
Effect: Broken characters (often displayed as �).
Fix: Use mb_substr() and other mb_* functions.
Using $str{0}
Effect: Parse error in PHP 8.
Fix: Use $str[0].
Best Practices
- Use single quotes for literal text and double quotes when interpolating.
- Store and output all text as UTF-8, and declare
<meta charset="UTF-8">in HTML. - Use
mb_*functions for anything a human reads. - Compare strings with
===; usestrcasecmp()for case-insensitive comparison. - Remember that string functions return new strings — assign the result.
- Trim and normalize user input before validating or storing it.
Official reference: https://www.php.net/manual/en/language.types.string.php