Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
executable file 34 lines (18 sloc) 7.08 KB

Функции работы с UTF-8 строками в PHP

Некоторые функции PHP (strlen, substr, а также обращение к строке как к массиву: $str[0]) не работают с многобайтовыми кодировками (вроде utf-8). В utf-8 1 символ закодирован с помощью от 1 до 6 байтов, а эти функции думают, что 1 буква всегда кодируется одним байтом. По этой причине они ломают символы, в результате получаются битые символы и ничего не работает. Потому вместо них надо использовать mb_ функции например mb_strlen, mb_substr. Вместо доступа к строке как к массиву надо использовать mb_substr.

Если тебе интересно, почему эти функции поддерживают только однобайтные кодировки, а не многобайтные, то причина в том, что они очень старые и написаны в то время (лет 40 назад) когда utf-8 и многобайтных кодировок еще не было. Подробно про кодировки и их историю я написал в отдельной статье про способ кодирования строк в памяти.

Давай разберем пример. Допустим, у нас есть строка из русской буквы «щ» в кодировке utf-8. Попытаемся взять первую букву с помощью неправильной функции:

// Внимание! это неправильный код, не пиши так!
$s = "щ";
$x = substr($s, 0, 1);

Буква «щ» кодируется в utf-8 как 2 байта: 209 137 (я взял информацию тут: http://www.utf8-chartable.de/unicode-utf8-table.pl?start=1024&utf8=dec ). substr отрезает от строки не первую букву, а первый байт. Это значит, что в $x он положит 1 байт с кодом 209. В utf-8 это неверная последовательность, она не соответствует никакому символу (так как после 209 обязательно должно идти второе число). Ideone может вообще отказаться что-то отображать, встретив такой код.

То же самое, когда ты обращаешься к строке как к массиву: $s[0]. Эта команда берет не первую букву, а только первый байт строки. Естественно, такая программа не будет работать.

Функция strlen считает число байт (не букв) в строке. То есть в данном случае strlen($s) вернет нам 2.

Латиница и цифры кодируются в utf-8 одним байтом, с ними это работает, но все равно, не надо использовать эти функции — это слишком ненадежно и легко сделать ошибку.

Также, чтобы работать с русскими (и другими нелатинскими) буквами в регулярках, надо ставить в конце флаг u: preg_match("/[абвг]/u", $string). Иначе preg_match будет думать что работает с однобайтной кодировкой и будет видеть не одну букву, а 2 latin1-символа (так как русская буква кодируется как 2 байта). Например, буква л кодирующаяся как 208 187 будет восприниматься как 2 символа с кодами 208 и 187, то есть л (кодировка latin-1: http://en.wikipedia.org/wiki/ISO/IEC_8859-1#Codepage_layout ). Таким образом, регулярка будет работать некорректно и найдет не то.

Вывод: используй mb_* функции. Не используй доступ к строке как к массиву. В регулярных выражениях используй флаг u (он говорит что используется utf-8 а не однобайтовая кодировка).

Некоторые строковые функции без префикса mb тем не менее корректно работают с utf-8 и их можно использовать. Вот они: strtr (если передавать массив), str_replace, str_repeat, explode, addslashes.

Функция trim()ltrim(), rtrim()) работает корректно с utf-8 только если мы отрезаем символы, кодирующиеся одним байтом (например, перенос строки, пробел или латинскую букву). В других случаях, если, например, написать trim('миша вова', 'м'), она воспринимает букву м, закодированную двумя байтами, как два символа, и корежит исходную строку.

Не работают с utf-8: strrev, strlen, substr, strpos, ucfirst, wordwrap, str_pad и большинство других строковых функций, для работы которых нужно считать число символов. Не работает задание ширины в функциях вроде sprintf и printf (они считают символ кириллицы за 2 символа при расчете длины строки).

mbstring.func_overload

В неоторых (неграмотных) учебниках ты можешь увидеть совет включить опцию mbstring.func_overload (подробнее про нее: http://php.net/manual/ru/mbstring.overload.php ). Она подменяет часть строковых функций вроде strlen на их mb-аналоги. Ни в коем случае так не делай, так как это изначально неправильно спроектированная опция. Она не решает проблему, для которой задумывалась (включить в старом приложении использующем функции вроде strlen поддержку utf-8), а лишь создает путаницу. Например, при ее включении strlen заменяется на поддерживающую utf-8 mb_strlen, но ucfirst, trim или sprintf ни на что не заменяется и не работает.

You can’t perform that action at this time.