Некоторые функции 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
.
Не работают с utf-8: strrev
, strlen
, substr
, strpos
, ucfirst
, wordwrap
, str_pad
и большинство других строковых функций, для работы которых нужно считать число символов. Не работает задание ширины в функциях вроде sprintf
и printf
.
В неоторых (неграмотных) учебниках ты можешь увидеть совет включить опцию mbstring.func_overload
(подробнее про нее: http://php.net/manual/ru/mbstring.overload.php ). Ни в коем случае так не делай, так как это изначально неправильно спроектированная опция. Она не решает проблему, для которой задумывалась (включить в старом приложении использующем функции вроде strlen
поддержку utf-8), а лишь создает путаницу. Например, при ее включении strlen
заменяется на поддерживающую utf-8 mb_strlen
, но ucfirst
ни на что не заменяется и не работает.