Unicode и PHP
Составлено с бесценной помощью Алексея ‘huNTer’ Колосова
В ближайшее время перекочует куда-нибудь в более подходящее для совместного редактирования место.
Если что-либо из указанного здесь не работает – обязательно оставьте об этом сообщение в комментариях. Любые замечания и исправления приветствуются (не забудьте сообщить платформу и версию PHP). Все нижеописанное действует для PHP 4.
###Строковые функции Новость плохая - стандартные “строковые” функции PHP как следует с UTF не работают. Новость получше - в PHP есть сделанный (конечно же японцами) модуль, который не является обязательным, но при этом реализует все необходимые функции сам (дублируя строковые функции своими с префиксом mb_). Новость совсем хорошая - все строковые функции PHP можно заменить их эквивалентами из mbstring, причем ваши скрипты, использующие строковые функции PHP, даже догадываться об этом не будут.
Для этого есть конфигурационная директива
mbstring.func\_overload
причем применять ее можно не только в корневом php.ini, но и (сюрприз) в вашем .htaccess. Следовательно – сначала проверяем по php_info, есть ли в комплекте сборки вашей версии PHP расширение mbstring. Возможно три варианта
- Ваш хостер не дает вам посмотреть php_info. Купить монтировку (она же, если пользоваться ЖЖ-терминологией, Титановый ЛомЪ) и умело ее применить. Бейте аккуратно, но сильно.
- Оного модуля в комплекте нет – пишите письмо админу и слезно просите собрать. Ссылку на этот текст можете приложить в комплекте.
- Модуль есть, все в порядке.
Если результат - пункт 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.