Multibyte Strings and Unicode
The web is multilingual: names like "José" and "Zoë", text in Greek, Hindi or Japanese, and emoji everywhere. PHP strings are sequences of bytes, while UTF-8 uses between one and four bytes per character, so byte-based functions can count, cut and change case incorrectly. The mbstring extension's mb_* functions work with characters instead.
Bytes versus characters
<?php $words = ["cat", "café", "Ελλάδα", "日本", "👍"]; foreach ($words as $w) { printf("%-12s bytes: %2d characters: %d\n", $w, strlen($w), mb_strlen($w)); }
cat bytes: 3 characters: 3 café bytes: 5 characters: 4 Ελλάδα bytes: 12 characters: 6 日本 bytes: 6 characters: 2 👍 bytes: 4 characters: 1
Notice that even the alignment is off: printf's width counts bytes, so multibyte words get less padding. ASCII characters take one byte each in UTF-8, which is why English-only code often seems to work fine — until the first customer named Zoë signs up.
Cutting strings safely
substr can cut a character in half, leaving invalid UTF-8 that displays as a "�". mb_substr counts characters:
<?php $name = "Renée Dubois"; echo substr($name, 0, 4), "|\n"; echo mb_substr($name, 0, 4), "|\n"; echo mb_substr($name, -6), "\n"; var_dump(mb_check_encoding(substr($name, 0, 4), 'UTF-8'));
Ren�| René| Dubois bool(false)
mb_check_encoding tells you whether a string is valid UTF-8 — worth checking on input from untrusted sources.
Changing case
strtoupper and ucfirst only handle ASCII letters. The multibyte versions understand every alphabet:
<?php $text = "élan vital in São Paulo"; echo strtoupper($text), "\n"; echo mb_strtoupper($text), "\n"; echo mb_convert_case($text, MB_CASE_TITLE), "\n"; echo mb_strtolower("ΑΘΗΝΑ"), "\n"; echo mb_ucfirst("über"), "\n";
éLAN VITAL IN SãO PAULO ÉLAN VITAL IN SÃO PAULO Élan Vital In São Paulo αθηνα Über
mb_ucfirst and mb_lcfirst were added in PHP 8.4, along with mb_trim, mb_ltrim and mb_rtrim.
Searching and splitting
Most string functions that deal with positions have multibyte counterparts: mb_strpos, mb_stripos, mb_strrpos, mb_substr_count. To split a string into characters, use mb_str_split:
<?php $word = "naïve"; echo strpos($word, "v"), " vs ", mb_strpos($word, "v"), "\n"; echo implode(" | ", mb_str_split($word)), "\n"; echo implode("", array_reverse(mb_str_split("ñandú"))), "\n"; echo strrev("ñandú"), "\n"; // byte reversal breaks characters
4 vs 3 n | a | ï | v | e údnañ ��dna��
Padding and trimming
<?php $names = ["Ana", "Zoë", "Łukasz"]; foreach ($names as $n) { echo "[", mb_str_pad($n, 8, "."), "]\n"; } echo "[", mb_trim("\u{3000}全角スペース\u{3000}"), "]\n"; // trims Unicode whitespace too
[Ana.....] [Zoë.....] [Łukasz..] [全角スペース]
mb_str_pad arrived in PHP 8.3. Use it whenever you align columns of text that might contain non-ASCII characters.
Characters and code points
mb_ord and mb_chr convert between a character and its Unicode code point, and the \u{…} escape writes any character into a double-quoted string:
<?php echo mb_ord("€"), "\n"; echo mb_chr(9731), "\n"; echo "\u{1F680} launch\n"; printf("U+%04X\n", mb_ord("é"));
8364 ☃ 🚀 launch U+00E9
Encodings
Use UTF-8 everywhere: save your source files as UTF-8, send Content-Type: text/html; charset=UTF-8 (PHP does this by default), set <meta charset="utf-8"> in HTML, and use utf8mb4 as the character set in MySQL. When you receive text in another encoding, convert it at the boundary:
<?php $latin1 = "\xE9t\xE9"; // "été" encoded as ISO-8859-1 var_dump(mb_check_encoding($latin1, 'UTF-8')); $utf8 = mb_convert_encoding($latin1, 'UTF-8', 'ISO-8859-1'); echo $utf8, "\n"; echo strlen($latin1), " bytes became ", strlen($utf8), " bytes\n";
bool(false) été 3 bytes became 5 bytes
Byte or multibyte?
- Use
mb_*functions for anything a human might type or read: names, titles, comments, messages. - Byte functions are fine (and faster) for things that are guaranteed ASCII: hex hashes, identifiers, HTTP header names, protocol data.
- Functions that don't depend on character boundaries —
str_contains,str_replace,explode,implode— work correctly on UTF-8 as they are.
Try it yourself
Write a function that shortens text to a number of characters and adds an ellipsis, without breaking multibyte characters.
Crème brûlée i… 日本語のテ… Short