Strings & text
Four ways to write a string, what interpolation really does, why a PHP string is bytes rather than characters, and the functions worth knowing.
A PHP string is a sequence of bytes. Not characters β bytes. That one fact explains most of what follows.
Four kinds of literal
<?php
$who = 'world';
echo 'single: no $who, no \n escapes', "\n";
echo "double: hello $who, and a real newline\n";
echo <<<TEXT
heredoc: behaves like double quotes,
interpolates $who, and the closing marker's
indentation is stripped from every line.
TEXT;
echo "\n";
echo <<<'TEXT'
nowdoc: behaves like single quotes.
$who stays literal.
TEXT;
echo "\n";Interpolation
Inside double quotes and heredocs, $name is replaced by its value. Simple array and property access works bare; anything more needs braces.
<?php
$user = ['name' => 'Ada', 'langs' => ['PHP', 'Go']];
$n = 2;
echo "Name: $user[name]\n"; // bare key β no quotes inside!
echo "First: {$user['langs'][0]}\n"; // braces for anything nested
echo "Double: {$n}00\n"; // braces to end the name earlyβ οΈ "$user['name']" is a parse error, but "$user[name]" works. That is a genuine inconsistency in the syntax: unquoted keys are only legal inside a simple interpolation. Use {$user['name']} everywhere and you never have to remember which is which.
Bytes, not characters
strlen() counts bytes. For anything that might not be ASCII, you want the mb_ functions.
<?php $s = 'cafΓ©'; echo 'strlen: ', strlen($s), "\n"; // 5 β Γ© is two bytes in UTF-8 echo 'mb_strlen: ', mb_strlen($s), "\n"; // 4 characters echo 'strtoupper: ', strtoupper($s), "\n"; // mangles the Γ© echo 'mb_strtoupper: ', mb_strtoupper($s), "\n"; echo 'substr: ', substr($s, 0, 4), "\n"; // cuts Γ© in half echo 'mb_substr: ', mb_substr($s, 0, 4), "\n";
π§ Scenario: A form truncates names to 20 "characters" with substr(). A user called JosΓ© near the limit gets a broken byte at the end, the database rejects the invalid UTF-8, and the bug only ever reproduces for non-English names. mb_substr() is the whole fix.
Indexing
A string can be indexed like an array β by byte offset. Negative offsets count from the end.
<?php $s = 'PHP'; echo $s[0], $s[1], $s[2], "\n"; echo $s[-1], "\n"; // P $s[0] = 'p'; // strings are mutable in place echo $s, "\n";
Concatenation and building
<?php
$a = 'Hello';
$b = 'world';
echo $a . ', ' . $b . "!\n";
$out = '';
foreach (range(1, 5) as $i) {
$out .= $i;
}
echo $out, "\n";
echo implode(', ', ['a', 'b', 'c']), "\n";
print_r(explode(',', 'a,b,c'));π‘ Tip: Building a long string with .= in a loop is fine in PHP β the engine grows the buffer in place. Collecting into an array and calling implode() once is still clearer when the pieces need a separator.
Formatting
<?php
printf("%s is %d years old\n", 'Ada', 36);
printf("%05.2f | %-8s| %'*10s\n", 3.14159, 'left', 'right');
printf("%b %o %x %X\n", 250, 250, 250, 250);
echo sprintf('%s', number_format(1234567.891, 2)), "\n";
echo str_pad('7', 3, '0', STR_PAD_LEFT), "\n";The functions you will actually use
<?php
$s = ' The Quick Brown Fox ';
echo '[', trim($s), "]\n";
echo strtolower(trim($s)), "\n";
echo ucwords('ada lovelace'), "\n";
echo str_replace('Quick', 'Slow', trim($s)), "\n";
echo strrev('stressed'), "\n";
echo str_repeat('-', 20), "\n";
var_dump(str_contains($s, 'Brown'));
var_dump(str_starts_with(trim($s), 'The'));
var_dump(str_ends_with(trim($s), 'Fox'));
echo strpos($s, 'Brown'), "\n";Comparing
<?php
var_dump('a' === 'a');
var_dump(strcmp('apple', 'banana') < 0); // ordering
var_dump(strcasecmp('PHP', 'php') === 0); // case-insensitive
var_dump(hash_equals('secret', 'secret')); // constant-time, for tokensβ οΈ Compare secrets β tokens, signatures, password hashes β with hash_equals(). === stops at the first differing byte, and the time it takes leaks how much of the guess was right.
Reference: Strings in the PHP Manual.