Xpoint
   [напомнить пароль]

Поддержка UTF-8 в PHP 5

Метки: [без меток]
2007-08-09 03:38:38 [обр] Михаил Харитонов[досье]

Скажите, какие действия надо предпринять, что бы работать с UTF-8 в PHP 5?
И какие при этом останутся ограничения?
Насколько я понял:

  1. Надо использовать нормальный текстовой редактор, способный сохранять в UTF-8 без BOM (Byte Order Mark).
  2. В preg_* функциях надо использовать модификатор \u.
  3. Вместо строковых функций (и функции mail() в часности) надо использовать их mb_* аналоги из библиотеки mbstring, или прописать в настройках PHP "mbstring.func_overload" для перегрузки стандартных функций. Тут вопрос: аналогов каких функций не существует в mbstring при работе с которыми в UTF-8 втречаются проблемы? Например, функции для сортировки строк (sort() и др.) я там не нашёл.
  4. Не использовать оператор {n} ($str{0}), т.к. он не корректно работает с многобайтовыми кодировками (так ли это в php 5?).
  5. Слышал где-то, что с функциех explode() какие-то проблемы возникают. Так ли это?

Что ещё я упустил?

спустя 8 часов [обр] arty(0/6)[досье]
  1. $string[n] не работает так же, как и $string{n}

еще мне пока что не удалось добиться нормальной работы с PREG_OFFSET_CAPTURE в preg_*, так же, как и mb_ereg_search_pos/mb_ereg_search_getpos: позиции возвращаются в байтах, а не в символах.

правда, вроде бы есть обходной путь — когда потом эти числа используются в mb_strpos или подобной, можно явно указать кодировку 'latin1' вместо стандартной utf8, но какой-то это кривой костыль.

спустя 1 час 10 минут [обр] Михаил Харитонов[досье]
еще мне пока что не удалось добиться нормальной работы с PREG_OFFSET_CAPTURE в preg_*, так же, как и mb_ereg_search_pos/mb_ereg_search_getpos: позиции возвращаются в байтах, а не в символах.
правда, вроде бы есть обходной путь — когда потом эти числа используются в mb_strpos или подобной, можно явно указать кодировку 'latin1' вместо стандартной utf8, но какой-то это кривой костыль.

И правда, костыль кривоват. Нашёл в инете (http://www.blueshoes.org/en/blog/) вот такое вот решение:

<?php
$text = "grЙЙn blue";
if (preg_match('/blue/u', $text, $matches, PREG_OFFSET_CAPTURE)) {
    foreach ($matches as &$match) {
        $byteStr = substr($text, 0, $match[1]);
        $chrLen  = mb_strlen($byteStr, 'UTF-8');
        if ($match[1] > $chrLen)
           $match[1] = $chrLen;   
    }
}
print '<pre>';
print_r($matches);
?>

Вроде работает на ура, только надо подумать как быть с substr, если mbstring.func_overload включён.

спустя 9 минут [обр] Михаил Харитонов[досье]
Ещё назрел вопрос: лучше разрабатывать скрипты на PHP5+UTF-8 с включённым mbstring.func_overload или нет? Какие-нибудь загвоздки возникают, если func_overload включён (например, как быть при необходимости обращения к перезруженным функциям)? По идее, лучше func_overload включить, т.к. существует большая вероятность, что где-нибудь по привычке да влепишь стандартную ф-ю, вместо mb_*.
спустя 49 минут [обр] arty(0/6)[досье]

честно говоря, этот вариант мне тоже кажется костылем

перегрузку мы используем в том числе для того, чтобы сторонние библиотеки не портили наш юникод : )

если нужно работать со строками как с байтами, например, при реализации gzip, указываем в mb_* функциях кодировку 'latin1', и проблем не возникает

спустя 22 минуты [обр] Михаил Харитонов[досье]
честно говоря, этот вариант мне тоже кажется костылем

Да по большому счёту, все такие решения будут костылями. :( Но если, как Вы и сказали,

$byteStr = substr($text, 0, $match[1]);

заменить на

$byteStr = mb_strcut($text, 0, $match[1], 'latin1');

то проблем с оверлоадом не возникнет и получаем более менее рабочий вариант. Вроде нормальный костыль (из тех, что так сразу можно прикинуть) получается, т.к. позволяет в итоге работать с возвращёнными номерами позиций PREG_OFFSET_CAPTURE как с позициями в UTF строках.

перегрузку мы используем в том числе для того, чтобы сторонние библиотеки не портили наш юникод : )

Точно, это я упустил. :)

спустя 1 час 36 минут [обр] Давид Мзареулян(536/1003)[досье]

Да, только некоторые сторонние библиотеки заодно используют strlen для определения байтовой длины строки (ну, и вообщзе иногда подразумевают, что работа идёт именно с байтами, а не с символами).

Универсального решения (оверлоадить/не оверлоадить) нет. И в том и в другом случае некоторые библиотеки придётся подхачивать:(

спустя 1 час 50 минут [обр] Михаил Харитонов[досье]

А никто не знает как дела со smarty обстоят после оверлоада?

Так же очень прошу поделиться опытом всех, кто разрабатывал приложения в среде PHP + UTF-8. Хочется узнать как можно больше о проблемах, на которые можно наткнуться прежде, чем приступить к разработке проекта под UTF.

спустя 1 час 3 минуты [обр] Давид Мзареулян(536/1003)[досье]

Ну вот я разрабатываю. Выбрал как раз вариант с оверлоадом, и кое-где напоролся на вышеописанное. Кроме этого, надо помнить, что не все функции оверлоадятся (скажем, strtoupper оверлоадится, но ucfirst — нет), смещения в preg_... считаются в байтах даже в режиме “/u” и т.д. Но в целом жить можно. Без оверлоада не пришлось бы хачить библиотеки, но фиг знает, где бы что ещё вылезло. Да и mb_… всё время писать некрасиво.

Как пример — вот что приходится делать с классами, которые считают, что strlen — это всегда байтовая длина:

class SphinxClientFx extends SphinxClient {
    public function Query ( $query, $index="*" ) {
        $enc = mb_internal_encoding();
        mb_internal_encoding("latin1");
        $res = parent::Query ( $query, $index );
        mb_internal_encoding($enc);
        return $res;
    }
    public function BuildExcerpts ( $docs, $index, $words, $opts=array() ) {
        $enc = mb_internal_encoding();
        mb_internal_encoding("latin1");
        $res = parent::BuildExcerpts ( $docs, $index, $words, $opts );
        mb_internal_encoding($enc);
        return $res;
    }
}

p.s. Вот со смарти не работал, не знаю.

спустя 12 минут [обр] arty(0/6)[досье]
Смарти нормально работает, хотя немного патчить приходится. Например, использовать output.trimwhitespace в исходном варианте не стоит. В патченом виде он, имхо, работает медленновато, поэтому мы просто отказались от него, полагаясь на гзип.
спустя 31 минуту [обр] Jimi Dini(9/9)[досье]

ну по большому счёту тут всё уже описали :)
усилено ждём php6 где будет правильная работа с юникодом

я при разработке unicode-проектов

  1. не пользуюсь оверлоадингом — явно использую mb_-функции
  2. вместо preg_* использую mb_ereg_* которые быстрее, более полно работают с юникодом и вообще удобнее (там изнутри используется библиотека oniguruma)
  3. сторонние библиотеки стараюсь не использовать
спустя 15 часов [обр] arty(0/6)[досье]
а можно ссылку на то, что ereg быстрее? я как-то до сих пор видел только противоположные мнения
спустя 43 минуты [обр] Давид Мзареулян(536/1003)[досье]
Возможно, они быстрее именно на юникод-данных, но ссылку всё равно хотелось бы.
спустя 3 дня [обр] Jimi Dini(9/9)[досье]

не просто ereg, а mb_ereg
это совершенно другая вещь

небольшое тестирование проводил я вот тут: http://blog.milkfarmsoft.com/?p=27
предлагаю всем желающим провести более плотное тестирование :)

спустя 29 минут [обр] Давид Мзареулян(536/1003)[досье]

Jimi Dini[досье] В контексте разговора было бы неплохо прописать в $pcre_regexp флаг “/u”.

Ну и потом, всё-таки PCRE позволяют делать некоторые удобные вещи, которые на ereg не сделаешь… За это им можно простить разницу в скорости в 20%:)

спустя 8 минут [обр] Давид Мзареулян(536/1003)[досье]

До кучи, мои результаты (php 5.2.0, windows, 10^6 оборотов на каждый цикл):

preg_match:      15.483505010605
mb_ereg:         9.3748888969421
mb_ereg_search:  4.8263139724731

Странно, что mb_ereg_search не так стремителен, как на Ваших тестах. Да, результаты с /u и без практически не отличаются.

спустя 4 часа 54 минуты [обр] Jimi Dini(9/9)[досье]

Давид Мзареулян[досье]

PCRE позволяют делать некоторые удобные вещи, которые на ereg не сделаешь…

например?
на всякий случай повторюсь: mb_ereg это не посиксовые регулярки а Oniguruma

спустя 14 минут [обр] Давид Мзареулян(536/1003)[досье]

Jimi Dini[досье] А вот этого я не знал (что они отличаются от посиксовых), спасибо. На сайте PHP про это ни гу-гу.

Чтоб долго не искать, может, Вы скажете — как они соотносятся с PCRE? Это точный порт или в чём-то есть разница? По первому взгляду на Вашу ссылку, вроде, всё что нужно есть.

спустя 23 минуты [обр] Jimi Dini(9/9)[досье]

это не порт pcre, а библиотека написанная с нуля. На Си-уровне у неё есть возможность работать в PERL/POSIX/RUBY/JAVA и ещё нескольких режимах.

mbstring судя по исходникам использует RUBY-режим

кстати, в файле который я указал выше есть список несовместимостей с PERL

спустя 1 минуту [обр] Давид Мзареулян(536/1003)[досье]
Спасибо!
спустя 14 часов [обр] Jimi Dini(9/9)[досье]

Вообще, mb_ereg очень странная штука… я покопался в исходниках — там можно настраивать режим совместимости

есть функция mb_regex_set_options
она возвращает текущие опции и позволяет выставить новые

по-умолчанию:

$ php -r 'var_dump(mb_regex_set_options());'
string(2) "pr"

параметры задаются строкой букв. расшифровка допустимых значений вот:

параметры (объединяются, т.е. можно указывать несколько):

'i': ONIG_OPTION_IGNORECASE;
'x': ONIG_OPTION_EXTEND;
'm': ONIG_OPTION_MULTILINE;
's': ONIG_OPTION_SINGLELINE;
'p': ONIG_OPTION_MULTILINE | ONIG_OPTION_SINGLELINE;
'l': ONIG_OPTION_FIND_LONGEST;
'n': ONIG_OPTION_FIND_NOT_EMPTY;

режимы (используется последний из указаных):

'j': ONIG_SYNTAX_JAVA;
'u': ONIG_SYNTAX_GNU_REGEX;
'g': ONIG_SYNTAX_GREP;
'c': ONIG_SYNTAX_EMACS;
'r': ONIG_SYNTAX_RUBY;
'z': ONIG_SYNTAX_PERL;
'b': ONIG_SYNTAX_POSIX_BASIC;
'd': ONIG_SYNTAX_POSIX_EXTENDED;

плюс к этому:

'e': eval()

расшифровка констант есть тут: API.txt

p.s. очень надеюсь что однажы у меня дойдут руки написать более вменяемый экстеншн на базе онигурумы…

спустя 1 месяц 29 дней [обр] Михаил Харитонов[досье]
Скажите, а правильно ли я понимаю, что проблемы при работе с мультибайтовыми кодировками в PHP возникают только в тех функциях и операторах, которые работают со строками, как с потоком байтов, а не символов?
Powered by POEM™ Engine Copyright © 2002-2005