Сайт Романа ПарпалакаЗаметкиТехнологииВеб-разработкаPHP и UTF-8

PHP и UTF-8

31 марта 2008 года

Если на одной веб-странице требуется использование символов из нескольких языков, то для такой страницы проще всего взять кодировку UTF-8. В связи с этим многие ругают PHP за отсутствие встроенной поддержки этой кодировки. Конечно, PHP может ее поддерживать, но это сделано через зад через расширения вроде mbstring. Признать такой способ удачным и универсальным нельзя.

Проблема состоит в том, что обычные функции для обработки строк, вроде strlen, используют принцип «один байт — один символ». Поэтому, если им передать строку, в которой некоторые символы закодированы несколькими байтами, могут произойти всякие неприятности.

Однако, если посмотреть на способ кодирования символов в UTF-8 и подумать, то станет ясно, что почти во всех ситуациях неприятностей можно избежать. Нужно помнить только, что такие функции, как strlen и substr, принимают на вход и выдают не номера символов, а номера байт. Регулярные выражения, str_replace и другие функции будут работать правильно.

Одна из редких ситуаций, в которых стандартных функций будет недостаточно, возникает тогда, когда от какой-нибудь строки нужно отрезать, например, ровно 10 символов. Скольким байтам в UTF-строке соответствуют эти символы, неизвестно. В таких ситуациях неплохо будет работать следующий код:

if (!function_exists('mb_internal_encoding'))
{
   function mb_strlen($str)
   {
      for ($i = strlen($str), $j = 0; $i--; )
         if ((ord($str[$i]) & 0xc0) != 0x80)
            $j++;
         return $j;
   }

   function mb_substr($str, $from, $len = false)
   {
      if ($from >= 0)
      {
         for ($c_byte = 0; $from--; )
            if (ord($str[$c_byte]) <= 0x7F)
               $c_byte++;
            else
               while ((ord($str[++$c_byte]) & 0xc0) == 0x80);

         $byte_beg = $c_byte;

         if ($len === false)
            return substr($str, $byte_beg);
         elseif ($len < 0)
         {
            for ($c_byte = strlen($str) - 1; $len++; $c_byte--)
               if (ord($str[$c_byte]) > 0x7F)
                  while ((ord($str[--$c_byte]) & 0xc0) == 0x80);

            return substr($str, $byte_beg, -strlen($str) + 1 + $c_byte);
         }
         else
         {
            for ( ; $len--; )
               if (ord($str[$c_byte]) <= 0x7F)
                  $c_byte++;
               else
                  while ((ord($str[++$c_byte]) & 0xc0) == 0x80);

            return substr($str, $byte_beg, $c_byte - $byte_beg);
         }
      }
      else
      {
         $last_byte = strlen($str) - 1;
         for ($c_byte = $last_byte; $from++; $c_byte--)
            if (ord($str[$c_byte]) > 0x7F)
               while ((ord($str[--$c_byte]) & 0xc0) == 0x80);

         $byte_beg = $c_byte;

         if ($len === false)
            return substr($str, $byte_beg - $last_byte);
         elseif ($len < 0)
         {
            for ($c_byte = $last_byte; $len++; $c_byte--)
               if (ord($str[$c_byte]) > 0x7F)
                  while ((ord($str[--$c_byte]) & 0xc0) == 0x80);

            return substr($str, $byte_beg - $last_byte, $c_byte - $last_byte);
         }
         else
         {
            for ( ; $len--; )
               if (ord($str[$c_byte]) <= 0x7F)
                  $c_byte++;
               else
                  while ((ord($str[++$c_byte]) & 0xc0) == 0x80);

            return substr($str, $byte_beg - $last_byte, $c_byte - $byte_beg);
         }
      }
   }
}
else
   mb_internal_encoding('UTF-8');

Теперь, чтобы взять первые 10 символов строки, достаточно написать mb_substr($str, 0, 10);.

Вот, в принципе, и всё. В следующей версии SiteX'а основной кодировкой будет UTF-8.

Поделиться
Посмотрите в блоге

Комментарии

#1. 7 августа 2008 года, 14:41. anonIMouS пишет:
С помощью регулярок все эти функции значительно упрощаются. Например,
function mb_strlen($str) {
return strlen(preg_replace('#[\x80-\xBF]#s','',$str));
}
#2. 7 августа 2008 года, 21:32. пишет:
Да, вы правы. И это решение работает быстрее, чем использование цикла.

На самом деле, мне сначала понадобилась функция utf8_substr. Вариант с регулярными выражениями для малых входных параметров (именно такие мне были нужны) работал медленнее, чем с циклом. Поэтому в utf8_strlen я оставил цикл.

Я исправлю заметку.
#3. 4 ноября 2008 года, 02:52. пишет:
Велосипед изобретаете

http://forum.dklab.ru/php/advises/Php-funk … Utf-8.html
#4. 4 ноября 2008 года, 20:33. пишет:
Возможно это и попытка изобрести велосипед.

Чтобы быть более конкретным, скажу, что речь шла об укорачивании слишком длинных ссылок:

http://punbb.informer.com/trac/browser/pun … r.php#L595

Сейчас там используется другой велосипед:

http://punbb.informer.com/trac/browser/pun … clude/utf8

У меня нет никакого желания выяснять, какой из велосипедов быстрее :)
#5. 21 февраля 2010 года, 19:08. zalivnoy пишет:
Вот еще про проблему обработки «русских» строк:
http://www.smartyit.ru/php/56

Оставьте свой комментарий

Ваше имя:

Комментарий:

Для выделения используйте следующий код: [i]курсив[/i], [b]жирный[/b].
Цитату оформляйте так: [q = имя автора]цитата[/q] или [q]еще цитата[/q].
Ссылку начните с http://. Других команд или HTML-тегов здесь нет.

Сколько будет 33+1?