Unicode для практикующих PHP-программистов

Используйте все языки в популярном языке Web-сценариев

При правильном подходе PHP эффективно обрабатывает не только иногда встречающиеся в английских именах и заимствованиях акцентированные символы, но и символы из других самых распространенных языков: немецкого, русского, китайского, японского и многих других.

Кэмерон Лэйрд, вице-президент, Phaseit,Inc.

Photo of Cameron LairdКэмерон Лэйрд (Cameron Laird) - бывший обозревательэтого сайта и в течение длительного времени пишет для developerWorks. Он часто рассказывает про Open Source проекты, позволяющие его работодателям ускорить разработку технологий в области надежности и безопасности передачи информации. Кэмерон впервые начал использовать AIX двадцать лет назад, когда тот был все еще экспериментальным продуктом. Все это время Кэмерон был заинтересованным пользователем и разработчиком средств для отладки памяти. Вы можете связаться с ним по адресу claird@phaseit.net.



09.04.2008

Выполните эту небольшую PHP-программу:

Листинг 1. Кодирование вывода информации на русском языке
               $q = "Здрав".
                    "ствуй".
                    "те";
               print html_entity_decode($q, ENT_NOQUOTES, 
                    'UTF-8')."\n";

Если все прошло удачно, вы увидите слово Здравствуйте - "Hello" или "Greetings" на русском языке.

Слишком часто работа в PHP с символами, отличными от стандартного английского алфавита, является делом везения или даже волшебства. Несмотря на то, что проделана огромная работа в таких областях как кодирование символов, интернационализация и т.д., большая ее часть сделана неправильно, или как минимум устарела, и очень многое зависит от конкретной конфигурации PHP. Целью данной статьи является представление всего лишь основ обработки Unicode в PHP, но это делается с достаточной аккуратностью и полнотой, что обеспечивает прочную основу для любого "интернационального программирования", которое нужно выполнить.

Очень многое происходит за кулисами

Эта очевидно простая программа из двух строк содержит очень много допущений. Прежде всего, я предполагаю использование PHP V5. Хотя можно работать с не английскими символами и на PHP V4, обычно это предполагает вовлечение не стандартных расширений, что определенно не уместно в 2007 году. В PHP V6, с другой стороны, планируется решить так много проблем кодирования символов, что это делает не нужными все методики, приведенные в данной статье. Есть надежда, что Unicode-строки в PHP V6 будут просто работать.

Даже при стандартной процедуре установки PHP V5 нет гарантий, что вы увидите те же результаты, которые вижу я. Во время разработки я попробовал несколько браузеров, которые не воспринимали русские шрифты и, следовательно, представляли информацию в латинской транслитерации: Zdravstvujte вместо Здравствуйте.


Формат кода

Исходный код на PHP в данной статье должен работать у подавляющего большинства разработчиков. Он применим (насколько это было возможно сделать) к любой стандартной установке PHP V5.

Чтобы сконцентрироваться на главном, исходный код представлен без стандартных охватывающих код тегов <?php и ?>. Выводимая информация в большинстве случаев имеет текстовый тип (text/plain). Если хотите, считайте листинг 1 урезанным листингом 2.

Листинг 2. Кодирование вывода информации на русском языке с более полным применением тегов
             <?php
                   // Следующие две строки необходимы только для 
                   // необычных конфигураций, но могут помочь.
               mb_language('uni');
               mb_internal_encoding('UTF-8');

               $q = "&#1047;&#1076;&#1088;&#1072;&#1074;".
                    "&#1089;&#1090;&#1074;&#1091;&#1081;".
                    "&#1090;&#1077;";
               print "<html>".
                     html_entity_decode($q, ENT_NOQUOTES, 'UTF-8').
                     "</html>";
             ?>

PHP V5 и стандартные установки браузеров охватывают подавляющее большинство возможных ситуаций. Почти все описанные ниже практические методики применимы к любой конфигурации php.ini, locale, набору шрифтов и т.д.

Предположим, что имеется совместимая платформа для наших экспериментов. Что мы с ней делаем? В большинстве случаев следующее:

  • Отображаем сообщение (prompt, ...) на языке, отличном от английского.
  • Принимаем данные от пользователя из полей TEXTAREA и TEXT INPUT.
  • Сохраняем (извлекаем) символьные данные в файлах и базах данных.
  • Выполняем простые строковые операции.

Давайте посмотрим, что это влечет за собой.


Две проблемы

Есть две трудности. Для преодоления ограничений стандартного английского алфавита, даже для поддержки акцентированных символов, иногда встречающихся в правильно оформленном английском ("Ramon," "Godel," "aperitif"), корректным решением для наших целей является Unicode, закодированный как UTF-8. Даже если вы знакомы с Unicode (см. раздел "Ресурсы"), это не простая тема со сложными специализированными определениями, включающими "glyph" (графический элемент), "code point" (элемент кода), "abstract character" (абстрактный символ) и многое другое. Разработка с использованием Unicode имеет все ту же проблему "начальной настройки", типичную для сетевого программирования, и даже еще хуже - вместо рабочего сервера и клиента, необходимых для приемлемого отображения результатов, эффективному Unicode-программированию требуются:

  • "Метод ввода" - именно такой, который хорошо подходит для символов, не доступных на обычной клавиатуре.
  • Приложение или язык вычислений, который корректно обрабатывает Unicode-данные.
  • Корректно установленные шрифты и другие средства для отображения в читабельном формате вычисленных вами символов.

Если вы много работаете с интернациональными проектами, то, возможно, используете специальные клавиатуры, редакторы, шрифты и т.д., для того чтобы можно было увидеть результаты работы.

Второй главной трудностью программирования такого рода является то, что PHP не работает с Unicode. Вернее, не работал. Он изначально не предназначался для работы с символами, не входящими в таблицу ASCII. PHP V6 должен исправить этот недостаток и поднять PHP на уровень таких языков как Python, в котором в строки можно напрямую встраивать Unicode-данные.

Между тем, Unicode-программирование с PHP требует осторожности и внимания. На многих форумах и в нескольких книгах по PHP, в которых упоминается Unicode, даются советы, полезные только с необычными расширениями, или предоставляются примеры кода, работающие только в некоторых конфигурациях. Это одна из причин того, что данная статья началась с листинга 1 - html_entity_decode корректно установлен у многих пользователей и редко перегружен. Хотя прием представления Unicode-данных в виде выраженных числами HTML-объектов приводит к непонятно выглядящему исходному коду, он легко синтезируется из стандартных Unicode-таблиц.

Этот же вывод можно закодировать даже более компактно:

         $r = "Здравствуйте";
         print "$r\n";

Однако в таком виде сам исходный код не является "чистым" семи- или даже восьмибитовым, и многие редакторы, системы управления конфигурациями и другие инструментальные средства разработки, вероятно, исказят его. Одним из последствий может стать упомянутая выше загадка: будет или не будет работать программа?

Еще один вариант, о котором стоит упомянуть:

          $q = "&#x417;&#x434;&#x440;&#x430;&#x432;".
               "&#x441;&#x442;&#x432;&#x443;&#x439;".
               "&#x442;&#x435;";
          print html_entity_decode($q, ENT_NOQUOTES,
                       'UTF-8')."\n";

Это ценная альтернатива листингу 1 для тех случаев, когда кто-то работает с таблицей Unicode-символов, выраженной в шестнадцатеричном, а не в десятичном формате.


Возможности PHP

Все, кроме самых понятных манипуляций с Unicode, я оформляю в удобных функциях, приведенных в листинге 3. Результаты работы приведены в листинге 4.

Листинг 3. Преобразование между отображаемыми UTF-8 и отлаживаемыми Unicode-кодами
          function utf8_to_unicode_code($utf8_string)
          {
              $expanded = iconv("UTF-8", "UTF-32", $utf8_string);
              return unpack("L*", $expanded);
          }
          function unicode_code_to_utf8($unicode_list)
          { 
              $result = "";
              foreach($unicode_list as $key => $value) {
                  $one_character = pack("L", $value);
                  $result .= iconv("UTF-32", "UTF-8", $one_character);
              }
              return $result;
          }
      
          $q = "&#1047;&#1076;&#1088;&#1072;&#1074;&#1089;".
               "&#1089;&#1090;&#1074;&#1091;&#1081;".
               "&#1090;&#1077;";
      
          $r = html_entity_decode($q, ENT_NOQUOTES, 'UTF-8');
          $s = utf8_to_unicode_code($r);
          $t = unicode_code_to_utf8($s);
          print "$r\n";
          print_r($s);
          print "$t\n";
Листинг 4. Результаты работы листинга 3
        Здравсствуйте
        Array
        (
            [1] => 65279
            [2] => 1047
            [3] => 1076
            [4] => 1088
            [5] => 1072
            [6] => 1074
            [7] => 1089
            [8] => 1089
            [9] => 1090
            [10] => 1074
            [11] => 1091
            [12] => 1081
            [13] => 1090
            [14] => 1077
        )
        Здравсствуйте

Обратите внимание на то, что весь исходный код и все, выводимое из строки на русском языке, отображается нормально и, на самом деле, является семибитовым ASCII, который легко копировать, отправлять по электронной почте и обрабатывать в обычных инструментальных средствах разработки.

Еще один способ вывести это же русское слово:

           $l = array(1047, 1076, 1088, 1072, 1074, 1089, 1089,
                 1090, 1074, 1091, 1081, 1090, 1077);
                 print unicode_code_to_utf8($l)."\n";

Обратите внимание на то, что поскольку ваши данные находятся на одной машине, допустимо пропустить первое целое значение 65279, маркер порядка байтов (Byte Order Marker - BOM). BOM документирован (см. раздел "Ресурсы") как аспект Unicode, не специфичный для PHP, и здесь упоминаться не будет.

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

Все другие толкования Unicode для PHP, которые я нашел разумными, рассматривают PHP как механизм перемещения символов из одного места в другое. Акцент делается на перемещении Unicode с клавиатуры в базу данных и на экран, поэтому нет необходимости проверять, как строки выглядят в самом PHP.

Это, несомненно, упрощает код, и для окончательных форм ваших рабочих приложений могут никогда не понадобиться HTML-объекты или преобразования UTF-32. Но я считаю эти низкоуровневые приемы работы бесценными для всех случаев, когда программирование не проходит гладко - например, когда база данных и ваш XML-редактор не понимают кодировки, и вы видите только символы "????????". В таких ситуациях очень помогает работа с отдельными символами в их различных читабельных для человека интерпретациях.


Соображения по программированию

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

  • Явно используйте кодировку UTF-8, маркированную:
    • "mb_language('uni'); mb_internal_encoding('UTF-8');" в верхней части ваших сценариев.
    • Content-type: text/html; charset=utf-8 в HTTP-заголовке для .htaccess, header() или конфигурации Web-сервера.
    • <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> и <orm accept-charset = "utf-8"> в HTML-коде.
    • CREATE DATABASE ... DEFAULT CHARACTER SET utf8 COLLATE utf8 ... ENGINE ... CHARSET=utf8 COLLATE=utf8_unicode_ci - обычная последовательность для экземпляра MySQL, а для других баз данных имеются похожие выражения.
    • SET NAMES 'utf8' COLLATE 'utf8_unicode_ci' - это полезная директива для PHP, которая передается в MySQL сразу после подключения.
    • В php.ini назначьте default_charset = UTF-8.
  • Заменяйте строковые функции, такие как strlen и strtlower, на mb_strlen и mb_convert_case.
  • Заменяйте mail и colleagues на mb_send_mail и др.; хотя понимающие Unicode сообщения электронной почты являются продвинутой темой, выходящей за рамки данной статьи, использование mb_send_mail является хорошей отправной точкой.
  • Используйте функции многобайтных регулярных выражений (multibyte regular expressions) (см. раздел "Ресурсы").

Функция ellipsis, которую я часто использую, представляет маленький пример работы с многобайтными строковыми функциями. Оригинальной версией этой функции была:

Листинг 5. Обычное усечение
    function ell_truncate($string, $permitted_length) {
        if (strlen($string) <= $permitted_length)
            return $string;
        $ellipsis = "...";
        return substr_replace($string, $ellipsis,
                        $permitted_length - strlen($ellipsis));
    }

Если использовать ее для long explanation и указать длину 10, возвратится long …, а при увеличении длины до 30 возвратится оригинальная строка. Это удобно, например, для быстрого создания аббревиатуры заголовков.

Ниже приведен пример более хитрого Unicode-решения.

Листинг 6. Более продвинутый ellipses
          function mb_ell_truncate($string, $permitted_length) {
              if (strlen($string) <= $permitted_length)
                  return $string;
              $ellipsis = html_entity_decode("&#x2026;",
                                   ENT_NOQUOTES, 'UTF-8');
              return mb_substr($string, 0,
                              $permitted_length -
                                       mb_strlen($ellipsis)).
                     $ellipsis;
          } 
      
          $q = "&#1047;&#1076;&#1088;&#1072;&#1074;".
               "&#1089;&#1090;&#1074;&#1091;&#1081;".
               "&#1090;&#1077;";
          $q = html_entity_decode($q, ENT_NOQUOTES,
                       'UTF-8');
          print mb_ell_truncate($q, 8)."\n";

Здесь используется стандартное оформление для многоточия, и корректно подсчитываются символы строки для аббревиации во всех комбинациях конфигураций PHP.

Все эти элементы - только отправная точка для Unicode-программирования. Остается множество более сложных проблем:

  • Не все языки различают верхний/нижний регистр символов.
  • Во многих случаях сортировка по алфавиту многозначна, поэтому она интерпретируется иначе, чем в английском языке.
  • Одни и те же символы могут сортироваться по-разному в зависимости от языка.
  • Система защиты преумножает сложность; то, что вы видите как "abc", может иметь совершенно другие значения, отличные от обычных английских символов, хотя выглядеть так же.

Эти проблемы являются общими для большинства понимающих Unicode вычислительных языков. Цель данной статьи - предоставить информацию, для того чтобы вы понимали основы достаточно глубоко и были готовы перейти к более продвинутым темам. Помните: если обработка Unicode выполняется у вас большим по объему или замысловатым кодом, возможно, вы делаете что-то не так. PHP V5 и приведенные выше советы должны помочь упростить программирование Unicode.


Заключение

Любой разработчик, прочитавший это введение и постигший основы, сможет программировать Unicode в PHP V5. Это не должно быть таинством.

Ресурсы

Научиться

Получить продукты и технологии

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Open source
ArticleID=300368
ArticleTitle=Unicode для практикующих PHP-программистов
publish-date=04092008