Bytes and characters: mb_*
So far every string in the examples was in Latin letters. Let's measure the length of a Russian word:
<?php
echo strlen('cat'), ' ', strlen('кот'), "\n"; // 3 6
var_dump('кот'); // string(6) "кот"Both words have three letters, yet strlen counted six in the Russian one. var_dump agrees: as you already know, the number in parentheses after string is the length of the string.
A string is bytes
Back to the row of tiles. A tile in a PHP string is a byte, the smallest cell of memory: it holds one number from 0 to 255.
A Latin letter, a digit or a space takes one tile. One tile is not enough for a Cyrillic letter, so it takes two.
The rule that lays letters out in bytes is called an encoding. Almost all files and websites today use the UTF-8 encoding: in it a Latin letter takes one byte, and a Cyrillic one takes two.
strlen, substr and str_pad count tiles, not letters. For Latin text that is the same thing; for Cyrillic it is not. To work with letters, PHP has functions with the mb_ prefix: they know a letter may take several bytes.
The mb_* functions come from the mbstring extension. Laravel Herd installs it along with PHP; on Linux it is a separate package. Without it, a call to mb_strlen stops the program with the error Call to undefined function mb_strlen().
Let's step through a program with the word привет. Press "Next step" and compare $bytes with $chars, then $upper with $mbUpper:
1<?php2 3$word = 'привет';4$bytes = strlen($word);5$chars = mb_strlen($word);6$upper = strtoupper($word);7$mbUpper = mb_strtoupper($word);8echo $bytes, ' ', $chars, "\n";9echo $upper, ' ', $mbUpper, "\n";
The word has six letters, and every Cyrillic letter takes two bytes.
The rule the widget shows:
strlen,substr,str_pad,strtoupperand the other ordinary functions work with bytes;mb_strlen,mb_substr,mb_str_pad,mb_strtoupperandmb_strtolowerwork with letters;- for Latin text both groups give the same result; for Cyrillic they do not.
Interviewers often ask why strlen returns twice the number of letters for Russian text. The answer: a PHP string is bytes, and a Cyrillic letter takes two bytes in UTF-8.
Letter case
Since PHP 8.2 the functions strtoupper, strtolower and ucfirst change only the Latin letters a to z. They return a Russian word as is, so for it you use mb_strtoupper and mb_strtolower. The function mb_ucfirst, which capitalizes the first letter, arrived in PHP 8.4.
Pieces and length by letters
Ordinary functions cut and pad a string by bytes, while the mb_ functions do it by letters:
$word = 'привет';
echo substr($word, 0, 4), "\n"; // пр
echo mb_substr($word, 0, 4), "\n"; // прив
echo str_pad('кот', 5, '*'), "\n"; // кот
echo mb_str_pad('кот', 5, '*'), "\n"; // кот**Let's go through it line by line:
substr($word, 0, 4)takes four bytes, which is two letters.mb_substr($word, 0, 4)takes four letters.str_pad('кот', 5, '*')adds nothing: the word already has six bytes, more than five.mb_str_padpads the word to five letters. This function arrived in PHP 8.3.
If you take an odd number of bytes, the piece breaks off in the middle of a letter, and an odd symbol shows up on the screen in its place. For the same reason $word[0] returns not the letter п but only its first byte.
This is where the tiles stop being an exact picture. A Cyrillic letter takes two tiles only when the file is saved in the UTF-8 encoding. Code editors save that way by default, but in a file in the old Windows-1251 encoding every letter takes one byte, and strlen('кот') gives 3.
Common mistake
The length of a name is checked with strlen:
$name = 'Александра';
if (strlen($name) > 10) { // ✗
echo 'name too long', "\n";
}The program prints name too long, although the name has exactly ten letters: strlen counted 20 bytes. PHP reports nothing, and the bug gets noticed when Russian names fail the check while English ones of the same length pass. The fix is mb_strlen($name) > 10.
Key points
- A PHP string is a row of bytes; in UTF-8 a Latin letter takes one byte, and a Cyrillic one takes two.
strlen('привет')is12, andmb_strlen('привет')is6.strlen,substr,str_padandstrtoupperwork with bytes, and their versions with themb_prefix work with letters.- Length, pieces and case of Russian text are handled with the
mb_*functions.