Перейти к содержимому
Теория · Строки · урок 7 из 9

Байты и символы: mb_*

До сих пор все строки в примерах были латиницей. Давайте посчитаем длину русского слова:

PHP
<?php

echo strlen('cat'), ' ', strlen('кот'), "\n"; // 3 6
var_dump('кот'); // string(6) "кот"

В обоих словах по три буквы, а strlen насчитала в русском шесть. var_dump согласна с ней: как вы уже знаете, число в скобках после string — длина строки.

Строка — это байты

Вернёмся к ряду плиток. Плитка в строке PHP — это байт, самая маленькая ячейка памяти: в ней помещается одно число от 0 до 255.

Латинская буква, цифра или пробел занимают одну плитку. Кириллической букве одной плитки мало, и она занимает две.

Правило, по которому буквы раскладываются по байтам, называют кодировкой. Почти все файлы и сайты используют кодировку UTF-8: в ней латиница занимает один байт, а кириллица — два.

strlen, substr и str_pad считают плитки, а не буквы. Для латиницы это одно и то же, для кириллицы — нет. Для работы с буквами в PHP есть функции с приставкой mb_: они знают, что буква может занимать несколько байт.

Функции mb_* даёт расширение mbstring. Laravel Herd ставит его вместе с PHP, а в Linux его ставят отдельным пакетом. Если расширения нет, вызов mb_strlen останавливает программу с ошибкой Call to undefined function mb_strlen().

Давайте пройдём по шагам программу со словом привет. Нажимайте «Следующий шаг» и сравнивайте $bytes и $chars, а потом $upper и $mbUpper:

ВживуюБайты и буквы в слове «привет»шаг 1 из 7 · рамок: 1

В слове шесть букв, и каждая кириллическая буква занимает два байта.

Сравните $bytes с $chars и $upper с $mbUpper

Правило, которое показывает виджет:

  • strlen, substr, str_pad, strtoupper и другие обычные функции работают с байтами;
  • mb_strlen, mb_substr, mb_str_pad, mb_strtoupper и mb_strtolower работают с буквами;
  • для латиницы обе группы дают одно и то же, для кириллицы — нет.

На собеседовании часто спрашивают, почему strlen для русского текста возвращает число вдвое больше, чем в нём букв. Ответ: строка в PHP — это байты, а кириллическая буква в UTF-8 занимает два байта.

Регистр букв

С PHP 8.2 функции strtoupper, strtolower и ucfirst меняют только латинские буквы от a до z. Русское слово они возвращают как есть, и для него берут mb_strtoupper и mb_strtolower. Функция mb_ucfirst, которая делает заглавной первую букву, появилась в PHP 8.4.

Кусок и длина по буквам

Обычные функции режут и дополняют строку по байтам, а функции mb_ — по буквам:

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"; // кот**

Разберём по строкам:

  1. substr($word, 0, 4) берёт четыре байта — это две буквы.
  2. mb_substr($word, 0, 4) берёт четыре буквы.
  3. str_pad('кот', 5, '*') ничего не добавляет: в слове уже шесть байт, больше пяти.
  4. mb_str_pad дополняет слово до пяти букв. Эта функция появилась в PHP 8.3.

Если взять нечётное число байт, кусок оборвётся посередине буквы, и на экране вместо неё появится непонятный значок. По той же причине $word[0] возвращает не букву п, а только её первый байт.

Здесь плитки перестают быть точной картинкой. Кириллическая буква занимает две плитки, только если файл сохранён в кодировке UTF-8. Редакторы кода сохраняют так по умолчанию, но в файле со старой кодировкой Windows-1251 каждая буква занимает один байт, и strlen('кот') даёт 3.

Частая ошибка

Длину имени проверяют через strlen:

PHP
$name = 'Александра';
if (strlen($name) > 10) { // ✗
    echo 'name too long', "\n";
}

Программа печатает name too long, хотя в имени ровно 10 букв: strlen насчитала 20 байт. PHP ничего не сообщает, и ошибку замечают, когда русские имена не проходят проверку, а английские такой же длины проходят. Исправление — mb_strlen($name) > 10.

Главное

  • Строка в PHP — ряд байт; в UTF-8 латинская буква занимает один байт, а кириллическая — два.
  • strlen('привет') — это 12, а mb_strlen('привет') — 6.
  • strlen, substr, str_pad и strtoupper работают с байтами, а их версии с приставкой mb_ — с буквами.
  • Для длины, кусков и регистра русского текста берут функции mb_*.