juliklive , venting since 2003

Unicode и PHP

Составлено с бесценной помощью Алексея 'huNTer' Колосова

В ближайшее время перекочует куда-нибудь в более подходящее для совместного редактирования место.

Если что-либо из указанного здесь не работает -- обязательно оставьте об этом сообщение в комментариях. Любые замечания и исправления приветствуются (не забудьте сообщить платформу и версию PHP). Все нижеописанное действует для PHP 4.


Строковые функции

Новость плохая - стандартные “строковые” функции PHP как следует с UTF не работают. Новость получше - в PHP есть сделанный (конечно же японцами) модуль, который не является обязательным, но при этом реализует все необходимые функции сам (дублируя строковые функции своими с префиксом mb_). Новость совсем хорошая - все строковые функции PHP можно заменить их эквивалентами из mbstring, причем ваши скрипты, использующие строковые функции PHP, даже догадываться об этом не будут.

Для этого есть конфигурационная директива

mbstring.func\_overload

причем применять ее можно не только в корневом php.ini, но и (сюрприз) в вашем .htaccess. Следовательно -- сначала проверяем по php_info, есть ли в комплекте сборки вашей версии PHP расширение mbstring. Возможно три варианта

  1. Ваш хостер не дает вам посмотреть php_info. Купить монтировку (она же, если пользоваться ЖЖ-терминологией, Титановый ЛомЪ) и умело ее применить. Бейте аккуратно, но сильно.
  2. Оного модуля в комплекте нет -- пишите письмо админу и слезно просите собрать. Ссылку на этот текст можете приложить в комплекте.
  3. Модуль есть, все в порядке.

Если результат - пункт 3, в наш магический .htaccess нужно прописать перегрузку стандартных строковых функций их версиями из расширения mb (эта перегрузка будет действовать не только на ваши скрипты, а вообще на все, исполняемое PHP-модулем внутри вашей веб-директории).

Однако - поддержка этой перегрузки различается от версии к версии PHP. Например в следующей конфигурации:

Apache v1.3.33-4, PHP v4.3.10-7, Debian GNU/Linux Sid.

не срабатывает mb_substr и перегрузка функций происходит неверно.

У меня на сервере

Apache/1.3.33, PHP v4.3.9, GNU/Linux i686

и на лаптопе

Apache 2, PHP v4.3.9, RELEASE_PPC Power Macintosh

работает вполне. Говоря кратко - надо проверять.

На случай, если mbstring не установлен (или вы не уверены, что в место него не придется использовать iconv), можно иметь (если ваша PHP-система большая) свои функции-обертки или пользоваться простым substr в расчете на то, что на системе, где скрипты будут развертываться, mb_string установлен (и поставлять рекомендации в пакете с системой, что дескать “If you need multibyte support you will need mbstring extension for PHP installed”).

На большинстве западных хостингов данное расширение присутствует, оно является включенным по умолчанию в сегодняшних дистрибутивах PHP 4.

Как настраивать mbstring

Опытным путем выявлена конфигурация Apache+PHP для адекватной реакции на UTF-8:

# unicode support
AddDefaultCharset utf-8
<IfModule mod_charset.c>
    CharsetDIsable on
    CharsetRecodeMultipartForms Off
</IfModule>

php_value       mbstring.func_overload  7
php_value       default_charset         UTF-8
php_value       mbstring.language       Russian

php_value       mbstring.internal_encoding      UTF-8

php_flag        mbstring.encoding_translation   on

php_value       mbstring.http_input     “UTF-8,KOI8-R,CP1251”

php_value       mbstring.http_output    UTF-8

php_value       mbstring.detect_order   “UTF-8,KOI8-R,CP1251”
# end

Добавить в .htaccess.

Имейте в виду, что если вы делаете раскодирование поступающих по POST или GET данных самостоятельно (это включает получение данных с помощью веб-сервисов вроде SOAP и XML-RPC) - encoding translation включать не надо.

Все скрипты, содержащие юникодные символы (то есть любые русские строковые литералы) следует отконвертировать в UTF-8, иначе у парсера PHP случится мягкий психоз. Нужная кодировка для скриптов - UTF-8, без BOM (byte order marker). Оный маркер превратится в браузерах в очень странный символ “неизвестный глиф” (похожий на вопросительный знак в ромбе), который мало того что будет появляться в браузере, но и будет выводиться перед вашими заголовками (поскольку оный маркер превратится в символ, попадающий в вывод ДО того как выполнится что-бы то ни было внутри PHP-блока.

Содержать скрипты в старой восьмибайтовой кодировке, выводя их в UTF-8 - абсурд.

Регулярные выражения

Еще одна плохая новость. Есть одна область PHP, которая к юникоду крайне чувствительна - это Perl-compatible regular expressions. Если, например, удобный formatter для написан без расчета на совместимость UTF, скорее всего такой форматтер с вашими текстами не заработает.

Если вы переводите на UTF уже написанный движок, первый источник ошибок следует искать там, где применяются регулярные выражения. Чаще всего в результате, например, неудачного preg_replace, результатом его работы становится нуль-строка. Когда эта нуль-строка попадает в базу в SQL-команде база наверняка выдаст вам ошибку ввода. Если все будет удачно, в небольшом скрипте вам придется произвести 5-10 хирургических замен, после чего все станет нормально.

preg_ при использовании с UTF-8 становится регистрозависимым. Причем только с кириллицей. Ключ /i игнорируется. Против этого помогает дополнительный ключ /u. Имейте в виду, что расширение mbstring на движок PCRE не повлияет (только на POSIX regular expressions, то есть на ereg_).

Другие функции

Все функции, рассчитанные на XML (DOM и SAX-парсер) настраиваются на UTF-8 согласно документации и дальнейших телодвижений не требуют, большинство общепринятых библиотек для PHP (типа PEAR и ADODB) чаще всего работают с UTF “из коробки”. То же относится к чтению/записи файлов и выводу в SDTOUT - если ваша консоль/терминал настроена на UTF-8 локаль проблем быть не должно.

Как проверить

Отконвертируйте все скрипты со строковыми литералами в UTF-8. Точно так же поступите с дампом базы. Перезалейте дамп, сотрите все кеш-файлы (если ваша система кеширует что-бы то ни было).

И посмотрите что получится. Если система сопрягается с другими (через web-сервисы, XML, отправку HTTP-запросов и т.д.) испытайте эту функциональность (желательно набором unit-тестов, если они у вас есть).

Введите во все текстовые поля слово Iñtërnâtiônàlizætiøn, поищите его встроенным механизмом поиска. Обязательно проверьте, как ведет себя Javascript (особенно если он должен кодировать строки для отправки серверу или делать поиск-замену в текстовых полях).

Старайтесь, чтобы был способ хранить в системе данные о том, в какой кодировке она в данный момент работает. Если вы пишете CMS - установите “контракт”, что кодировка прописывается в файле настроек или выбирается пользователем, храните эту кодировку в переменной или константе. Отправляйте ее в HTTP-заголовках и в meta-теге документов. Когда требуется сгенерировать что-либо для обмена с внешним миром (отправить из скрипта e-mail, trackback ping, использовать чужой веб-сервис) считывайте эту настройку и конвертируйте все строки в UTF-8 автоматически, опираясь на нее. Перед отправкой данных по HTTP всегда приписывайте charset в конце заголовка Content-type (чтобы сделать это для отправляемых форм пропишите им accept-charset).

И наконец

Требуйте Unicode-совместимость у ближайшего к вам PHP-разработчика. Стучите ему в таз, батарею и тумбочку, пишите внятные багрепорты, помогайте и ассистируйте. И дайте ему ссылку сюда - вдруг пригодится.

Более подробное объяснение как заставить PHP работать с Юникодом можно найти на соответствующей странице WACT.

Suspects: Веб-стройка Юникод

What others said

huNTer

эээ... huNTer!!! это принципиально :)

kukutz

К сожалению, ни слова о mysql и печальной совместной жизни mysql < 4.1, FTS и Unicode.

Julik

Во-первых, про MySQL должен быть отдельный рассказ.

Во-вторых, все мои проекты (кроме этого блога) уже года два сидят на Postgres, где ситуация несколько более прозрачна, поэтому с MySQL я уже довольно давно не имел опыта.

Если есть желание - помогите с написанием ;-)

huNTer

Написал облегченный вариант typografic'и. Раземеется не полный, но все лучше чем ничего.

Julik

Так опубликовал бы!

Slach

мы тут тоже пробовали мигрировать с MySQL4.0.x на MySQL4.1.x и utf8

сначала девелоперские тестовые серваки под win32 потом тестинг сервер под Debian и скоро вот думает подымать продакшен сервер на mysql4.1

нахватались всякого под win32 я попробовал накопленный опыт выразить вот тут http://phpclub.ru/talk/showthread.php?s=&threadid=67767

Julik

На Win32 я принципиально не работаю :-) а с MySQL 4.1 у меня все обстоит вполне шоколадно (на линуксах/макосях) - просто вызываю SET NAMES UTF8 при установке соединения с базой. Естественно для хостера такой вариант не подойдет, но мне - более чем.

Ну и плюс - да, настроить дефолтные чарсеты и collation нужно (чтобы таблицы создавались с правильным чарсетом.

Slach

Уважаемый Julik, не подскажете случайно чего нибудь полезного вот по этому поводу http://www.livejournal.com/users/slach/187305.html ?

Slach

Уважаемый Julik, не подскажете случайно чего нибудь полезного вот по этому поводу http://www.livejournal.com/users/slach/187305.html ?

Julik

Увы, нет - а времени сейчас разбираться нету. Анимировать надо - экзамены идут :-)

Nigadiay

Julk, я какзаз разбирался сейчас с юникодом плюс пхп. Очень полезная статья, спасибо. Но вот у меня такая проблема: при созранении файлов в кодировке utf-8 все равно какая-то гадость сохраняется в начале файла. Хотя сохраняю без БОМ. И конечно же случается та страшная проблема с заголовками. Может я плохо сохраняю? В чем может быть проблема, не подскажешь?

Julik

smenite tekstovii redaktor kotorim vi polzuetes

shamanStillSir

Спасибо большое. Неимоверно помогло.

rin-nas

Набор PHP функций для разработчиков веб-сайтов, использующих кодировку UTF-8 http://forum.dklab.ru/viewtopic.php?t=17146

Have a word?


Please do not go bonkers with code blocks, links and viagra ads, we are taking measures