Мне каждый раз задают один и тот же вопрос, спрашивают об одном и том же: «Как перекодировать кракозябры из базы данных, хранящей строки в кодировке latin1 в нормальную кириллицу (windows-1251) или utf-8».
Ниже я постараюсь наиболее полно ответить на данный вопрос, а также приведу кусок кода на PHP, который однозначно решает проблему.
Во-первых, я никому не рекомендую продолжать работать в кодировке windows-1251. Эта однобайтовая кодировка уже не удовлетворяет требованиям современности. Поскорее переводите все проекты на utf-8. Чем быстрее это будет сделано, тем быстрее пропадут у вас проблемы с кракозябрами.
Теперь о latin1. Эта кодировка (также известна как windows-1252) повсеместно использовалась ранее в MySQL вплоть до версии 4. Символьная таблица кириллических букв находится в ней на месте арабских символов. Но поскольку она тоже однобайтовая, то при чтении данных в этой кодировке из этой таблицы и выводе их как windows-1251 не возникает никаких проблем, ведь коды в итоге те же самые (0xA0-0xFF). Но всё это будет работать только до тех пор, пока вы не установите MySQL 5+, работающий по дефолту в utf-8.
Что же делает MySQL 5+, передавая Вам такие данные? Перед передачей на сторону клиента он честно перекодирует все данные в utf-8, помещая арабские символы (а в latin1 ваша кириллица на самом деле является арабскими символами) в тот диапазон кодов utf-8, где они и должны быть. В результате если вы даже попытаетесь перекодировать полученную utf-8-строку обратно в кириллицу функцией iconv(‘utf-8’, ‘windows-1251’, $str), то у вас ничего не получится. iconv выдаст ошибку, либо вернёт пустую строку.
Первое, что делает программист — он пытается изменить кодировку таблицы latin1 на windows-1251 в phpMyAdmin. Но MySQL этого сделать не может (о чём он и пишет), ведь в кодировке windows-1251 нет соответствующих арабских символов. Второе, что приходит в голову — сконвертировать эту таблицу в utf-8. И это получается. Только вот тексты по-прежнему отображаются кракозябрами.
Как же быть? Как решить эту проблему ?
Решение тут довольно простое, но чтобы к нему прийти самостоятельно, надо чётко понимать — что такое кодировки и как они работают. В понимании поможет моя hand-made диаграмма.
А вот и алгоритм, которым я пользуюсь, чтобы привести кодировки в порядок.
- Перевожу все таблицы БД в кодировку utf-8. При этом якобы кириллические символы, хранящиеся в кодировке latin1, а поэтому на самом деле являющиеся арабскими, переводятся в utf-8 и занимают свои законные места в диапазоне кодов utf-8, предназначенном для арабских символов.
- Пишу микроутилиту на PHP, которая делает следующее с каждой символьной строкой:
- а) Переводит строку в кодировку windows-1252. Тут проблем быть не должно. Тем самым арабские буквы занимают диапазон кодов A0-FF.
- б) Переводит полученную однобайтовую строку в utf-8, но уже не как windows-1252, а как windows-1251, т.е. выдавая символы из диапазона A0-FF за кириллические. В итоге символы попадают в utf-8 в тот диапазон кодов, который предназначен для кириллических символов.
- Всё, теперь наша строка официально является кириллической строкой в utf-8. Её можно записать обратно в ту же ячейку БД, либо сразу выдать в выходной поток. Однако я всё же рекомендую выполнить однократное полное преобразование БД, и забыть о latin1 как о страшном сне.
Ниже привожу сэмпл кода на PHP, который переводит ФИО пользователей в нормальную кириллическую кодировку.
$q = 'select id, fio from `users`'; $res = mysql_query($q); while (($row = mysql_fetch_assoc($res)) !== false) { // Преобразуем fio из utf-8/latin1 в windows-1252 $s = iconv('utf-8', 'windows-1252', $row['fio']); // Преобразуем строку из однобайтной кодировки обратно в utf-8, выдав её за windows-1251 $s = iconv('windows-1251', 'utf-8', $s); // Сохраняем назад в БД $q = 'update `users` set fio = "'.addslashes($s).'" where id = '.$row['id']; mysql_query($q); }