Skip to content
Theory · Strings · lesson 7 of 9

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
<?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:

LiveBytes and letters in the word "привет"step 1 of 7 · frames: 1

The word has six letters, and every Cyrillic letter takes two bytes.

Compare $bytes with $chars and $upper with $mbUpper

The rule the widget shows:

  • strlen, substr, str_pad, strtoupper and the other ordinary functions work with bytes;
  • mb_strlen, mb_substr, mb_str_pad, mb_strtoupper and mb_strtolower work 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:

PHP
$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:

  1. substr($word, 0, 4) takes four bytes, which is two letters.
  2. mb_substr($word, 0, 4) takes four letters.
  3. str_pad('кот', 5, '*') adds nothing: the word already has six bytes, more than five.
  4. mb_str_pad pads 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:

PHP
$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('привет') is 12, and mb_strlen('привет') is 6.
  • strlen, substr, str_pad and strtoupper work with bytes, and their versions with the mb_ prefix work with letters.
  • Length, pieces and case of Russian text are handled with the mb_* functions.