При разработке веб-приложений есть три важных момента, связанных с кодировками: информация в файлах-сценариях, информация в базе данных и браузер пользователя. Если выставить хотя бы одну кодировку неверно, то, в лучшем случае, данные отобразятся неверно, в худшем, безвозвратно потеряются. Чтобы этого не произошло, а приложение работало корректно при любых настройках сервера, нужно правильно выставить кодировки.
Работа с локалями в PHP выглядит одинаково и в UNIX, и в Windows, и в любой другой платформе. Для установки значений локали служит всего одна функция setlocale()
. Чтобы выставить локаль, нужно передать функции первым аргументом категорию, на которую эта локаль распространяется, последующими список возможных локалей. Результатом будет название первой подходящей локали, которая и была установлена.
Пример - установка и использование локали |
|
<?php // Установка локали echo setlocale(LC_ALL, 'ru_RU.CP1251', 'rus_RUS.CP1251', 'Russian_Russia.1251'); // Выведет ru_RU.CP1251 для FreeBSD // Выведет rus_RUS.CP1251 для линукса // Выведет Russian_Russia.1251 для Windows // ... // Вывод локализованных сообщений, например, даты echo '<br />', strftime('Число: %d, месяц: %B, день недели: %A'); ?> ru_RU.CP1251 Число: 10, месяц: октября, день недели: пятница или Russian_Russia.1251 Число: 10, месяц: Октябрь, день недели: пятница |
Для того, чтобы узнать, какие локали доступны в Windows, нужно зайти в панель управления, "Язык и региональные стандарты".
На вкладке "Дополнительно", в разделе "Кодовые страницы таблиц преобразования" показан список всех возможных локалей для Windows, которые можно использовать в PHP. Кодовые страницы, которые отмечены в списке, из PHP могут быть использованы по их номеру. В общем случае, использование выглядит по следующей схеме: Для России это может выглядеть как Для Украины - Вместо длинных названий можно использовать сокращённые |
Единственная кодировка, с которой у меня возникли проблемы, как ни странно, оказалась UTF-8. При попытке выставить эту кодировку, выставляются все категории локалей, кроме основной. Вывод локализованных сообщений при этом идёт в cp1251.
Пример - установка локали UTF-8 на Windows |
|
<? // Кодировка страницы windows-1251 header('Content-Type: text/html; charset=windows-1251'); echo '<pre>'; // Локаль устанавливаем UTF-8 echo setlocale(LC_ALL, 'Russian_Russia.65001'), PHP_EOL; // Но данные будут выводиться всё равно в cp1251 :((( echo strftime('%A'), PHP_EOL; ?> LC_COLLATE=Russian_Russia.65001;LC_CTYPE=Russian_Russia.1251; LC_MONETARY=Russian_Russia.65001;LC_NUMERIC=Russian_Russia.65001; LC_TIME=Russian_Russia.65001 пятница |
Пока это можно списать на внутренний механизм PHP работы со строками. С шестой версии PHP вся обработка строк должна будет вестись в UTF-8, но до тех пор надо просто знать об этом и делать поправку.
Ещё одной странностью при работе с локалями в PHP на Windows является неправильная работа с категориями локалей. Так, например, я выставляю локаль на функции времени KOI8-R, setlocale(LC_TIME, 'Russian_Russia.20866')
, но почему-то выставляется cp1251 на все категории. Суть проблемы я так и не понял, возможно, это просто баг (проверялось на PHP 5.2.3), а возможно, что внутренний механизм Windows просто не позволяет этого делать. Хотя по мне, так это чистой воды баг.
В общем-то, на этом можно и закончить разговор о локалях на Windows. Главное, запомнить, что локали, которые портированы из UNIX, под WIndows работают только для "галочки". Шаг влево, шаг вправо и результат будет непредсказуемым. Безопасно можно использовать только cp1251 (windows-1251) и KOI8-R, и только для LC_ALL
.
Код - установка локали на Windows |
|
<?php // Устновка локалей для Windows // Кодировка Windows-1251 setlocale(LC_ALL, 'Russian_Russia.1251'); // Кодировка KOI8-R setlocale(LC_ALL, 'Russian_Russia.20866'); // Кодировка UTF-8 (использовать осторожно) setlocale(LC_ALL, 'Russian_Russia.65001'); ?> |
Выше я описал работу с локалями в Windows, теперь можно заострить внимание на UNIX-like системах. Для простоты, я буду их называть UNIX, а подразумевать FreeBSD :). В контексте данной статьи это не особо важно.
Итак, дистрибутивы UNIX поставляются в одном виде для всех, и работа рассчитана на многопользовательский режим, поэтому о правильной настройке локали должен заботиться сам пользователь, например:
zg# locale LANG= LC_CTYPE="ru_RU.KOI8-R" LC_COLLATE="ru_RU.KOI8-R" LC_TIME="ru_RU.KOI8-R" LC_NUMERIC="ru_RU.KOI8-R" LC_MONETARY="ru_RU.KOI8-R" LC_MESSAGES="ru_RU.KOI8-R" LC_ALL=ru_RU.KOI8-R zg# |
Так может выглядеть работа системной команды locale
, которая выводит текущие настройки локали для пользователя. А так, обычно, выглядят настройки локали для пользователя, под которым работает PHP:
passthru('locale'); ================ LANG= LC_CTYPE="C" LC_COLLATE="C" LC_TIME="C" LC_NUMERIC="C" LC_MONETARY="C" LC_MESSAGES="C" LC_ALL= |
Функция ucwords() должна была сделать заглавными первые буквы всех слов. А перед этим strtolower() должна была предварительно все заглавные буквы сделать строчными. Но ничего не произошло. Так же не будет работать следующий код:
echo ucwords(strtolower('привет, МИР!')); ================ привет, МИР! |
Хотя \w является множеством знаков, из которых может состоять слово (алфавит, цифры и _), регулярное выражение не срабатывает. Причина как раз в том, что, работая с cp1251, мы не сказали об этом php. Чтобы исправить положение, достаточно воспользоваться функцией setlocale() и указать правильную локаль, например, так:
setlocale(LC_ALL, 'ru_RU.CP1251'); |
Здесь первый аргумент - это категория, на которую будет распространяться локаль (константа LC_*), второй - название локали. Начиная с версии 4.3.0 можно указывать несколько имён локалей в виде массива или в качестве дополнительных аргументов. После вызова функция установит первую подходящую локаль и вернёт её имя:
echo setlocale(LC_ALL, 'cp1251', 'koi8-r', 'ru_RU.KOI8-R'); ================ ru_RU.KOI8-R |
С помощью команды grep
я отобрал локали, которые поддерживают русский язык. Любую из них можно использовать, однако следует понимать, что данные должны быть в кодировке, на которую рассчитана локаль. Если же это правило не будет соблюдено, то результат может оказаться весьма неожиданным:
echo setlocale(LC_ALL, 'ru_RU.KOI8-R'), PHP_EOL; echo ucwords(strtolower('привет, МИР!')); =============== ru_RU.KOI8-R пРИВЕТ, мИР! |
Если учесть, что koi8-r достаточно популярная кодировка для UNIX-севреров, а windows-1251 для русскоязычных сайтов, то подобное "необычное" поведение не такая уж и редкость. Когда-то я и сам столкнулся с этой проблемой при портировании проекта на реальный хостинг.
После установки правильной локали все примеры, которые не работали выше, будут работать как нужно!
echo setlocale(LC_ALL, 'ru_RU.CP1251', 'rus_RUS.CP1251', 'Russian_Russia.1251'), PHP_EOL; echo ucwords(strtolower('привет, МИР!')), PHP_EOL; echo preg_match('/^\w+$/', 'привет') ? 'нашёл' : 'не работает', PHP_EOL; echo strftime('Сегодня: %A, %d %B, %Y года'); =============== ru_RU.CP1251 Привет, Мир! нашёл Сегодня: суббота, 12 июля, 2008 года |
По-русски заговорит и функция strftime(), которая корректно работает с локалями, а также и всё остальное, что зависит от локали.
Напомню, что возможность задавать кодировки появилась только в MySQL 4.1.11 и выше.
В отличие от php, проблемы с кодировками базы данных проявляют себя гораздо быстрее, чем проблемы с локалью. И связано это прежде всего с хранением и выборкой данных, поскольку от этого зависит информация на сайте. Я не буду подробно расписывать все тонкости, поскольку есть отдельная статья, остановлюсь на самых важных моментах.
Первое, чему необходимо научиться, смотреть текущие настройки соединения с mysql:
mysql> show variables like 'char%'; +--------------------------+----------------------------------+ | Variable_name | Value | +--------------------------+----------------------------------+ | character_set_client | cp1251 | | character_set_connection | cp1251 | | character_set_database | cp1251 | | character_set_filesystem | binary | | character_set_results | cp1251 | | character_set_server | cp1251 | | character_set_system | utf8 | | character_sets_dir | /usr/local/share/mysql/charsets/ | +--------------------------+----------------------------------+ 8 rows in set (0.00 sec) |
Критичными для пользователя являются character_set_client и character_set_results, которые отвечают за кодировку, в которой данные поступают в базу, и кодировку, в которой данные поступают из базы к пользователю. Если эти две кодировки отличаются от той, в которой работает клиент, в нашем случае php-скрипты, то неминуемо будут "странности", например, при сортировке выборки или внесении данных в базу.
Второе, что необходимо знать, как правильно сообщить mysql о кодировках. Самый простой и правильный способ, это использовать запрос set names:
mysql> set names 'cp1251'; Query OK, 0 rows affected (0.00 sec) |
После этого три переменные character_set_client, character_set_connection и character_set_results примут значение cp1251. Это будет означать - клиент работает в кодировке windows-1251 (cp1251).
Помимо этого можно устанавливать непосредственно серверные переменные:
mysql> set character_set_client='UTF8'; Query OK, 0 rows affected (0.00 sec) mysql> show variables like 'char%'; +--------------------------+----------------------------------+ | Variable_name | Value | +--------------------------+----------------------------------+ | character_set_client | utf8 | | character_set_connection | cp1251 | ..... |
Теперь данные поступают и извлекаются в разных кодировках.
Список доступных кодировок можно просмотреть так:
mysql> show charset; +----------+-----------------------------+---------------------+--------+ | Charset | Description | Default collation | Maxlen | +----------+-----------------------------+---------------------+--------+ | dec8 | DEC West European | dec8_swedish_ci | 1 | | cp850 | DOS West European | cp850_general_ci | 1 | | hp8 | HP West European | hp8_english_ci | 1 | | koi8r | KOI8-R Relcom Russian | koi8r_general_ci | 1 | | latin1 | cp1252 West European | latin1_swedish_ci | 1 | | latin2 | ISO 8859-2 Central European | latin2_general_ci | 1 | | swe7 | 7bit Swedish | swe7_swedish_ci | 1 | | ascii | US ASCII | ascii_general_ci | 1 | | hebrew | ISO 8859-8 Hebrew | hebrew_general_ci | 1 | | koi8u | KOI8-U Ukrainian | koi8u_general_ci | 1 | | greek | ISO 8859-7 Greek | greek_general_ci | 1 | | cp1250 | Windows Central European | cp1250_general_ci | 1 | | latin5 | ISO 8859-9 Turkish | latin5_turkish_ci | 1 | | armscii8 | ARMSCII-8 Armenian | armscii8_general_ci | 1 | | utf8 | UTF-8 Unicode | utf8_general_ci | 3 | | cp866 | DOS Russian | cp866_general_ci | 1 | | keybcs2 | DOS Kamenicky Czech-Slovak | keybcs2_general_ci | 1 | | macce | Mac Central European | macce_general_ci | 1 | | macroman | Mac West European | macroman_general_ci | 1 | | cp852 | DOS Central European | cp852_general_ci | 1 | | latin7 | ISO 8859-13 Baltic | latin7_general_ci | 1 | | cp1251 | Windows Cyrillic | cp1251_general_ci | 1 | | cp1256 | Windows Arabic | cp1256_general_ci | 1 | | cp1257 | Windows Baltic | cp1257_general_ci | 1 | | binary | Binary pseudo charset | binary | 1 | | geostd8 | GEOSTD8 Georgian | geostd8_general_ci | 1 | +----------+-----------------------------+---------------------+--------+ 26 rows in set (0.00 sec) |
И третье, что необходимо знать, - правила создания таблиц для хранения данных в нужной кодировке. К слову, данные можно хранить в любой кодировке, а работать с ними в кодировке клиента. Однако, важно понимать, что кодировки носят национальный характер и должны соответствовать вносимым данным. Иначе будут потери. Для русского языка есть три национальных кодировки koi8r, cp866, cp1251, которые могут конвертироваться друг в друга без потерь. Также можно использовать интернациональную кодировку UTF8.
Кодировку можно выставить на базу данных, таблицу и поле таблицы. Так, например, можно создать базу данных в кодировке koi8r:
CREATE DATABASE `test` DEFAULT CHARACTER SET koi8r;
Следует отметить, что кодировка базы данных влияет только на дефолтные значения кодировок при создании таблиц. Это значит, что неважно в какой кодировке была создана база, если кодировка таблицы была задана явно. Это же правило относится и к полям таблицы.
Следующим шагом я создам таблицу в cp1251 и одним полем в utf8:
CREATE TABLE `t` ( `id` VARCHAR( 60 ) NOT NULL , `data` TEXT CHARACTER SET utf8 NOT NULL , PRIMARY KEY ( `id` ) ) TYPE = MYISAM CHARACTER SET cp1251; |
После того, как таблица создана с нужными параметрами кодировки, mysql автоматически начинает переводить данные при внесении и выборке.
mysql> select * from t; +--------+-------------+ | id | data | +--------+-------------+ | привет | привет мир! | +--------+-------------+ 1 row in set (0.00 sec) |
Данные хранятся в разном виде, но поступают к пользователю именно так, как надо!
Подробнее с кодировками и проблемами их использования можно ознакомиться на http://dev.mysql.com/doc/refman/5.1/en/charset.html.
Объявить кодировку html-страницы можно двумя способами: через заголовки и мета-тег в самой странице. Мета-тег используется только в статичных страницах.
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251"> |
Я не буду его разбирать, это проблемы html. Во всех остальных случаях предпочтительней использовать HTTP-заголовок Content-Type.
PHP позволяет работать с HTTP-заголовками посредством функции header():
// Объявление типа содержимого и его кодировки header('Content-Type: text/html; charset=windows-1251'); |
Но браузер отобразит страницу корректно только в том случае, когда php-файлы сами были созданы в кодировке cp1251. Также нужно понимать, что заголовки должны быть отправлены до любого вывода на экран.
При необходимости перекодировать страницы "на лету", достаточно воспользоваться буферизацией и iconv:
Код - динамическая перекодировка |
|
1 |
<?php iconv_set_encoding('internal_encoding', 'WINDOWS-1251'); // Исходная кодировка файлов iconv_set_encoding('output_encoding' , 'UTF-8'); // Конечная кодировка ob_start('ob_iconv_handler'); // буферизация header('Content-Type: text/html; charset=UTF8'); ?> Привет, мир! |
Надпись "Привет, мир!" будет выведена в юникоде, при этом браузер получит информацию о кодировке через заголовки и правильно отобразит страницу. Но важно понимать, что внутри скрипта и при соединении с базой данных надо использовать windows-1251 (cp1251), поскольку страница должна быть сформирована в одной кодировке.
Важно помнить, что функции iconv доступны не всегда, и проверка на доступность этих функций не будет лишней.
Для безопасной разработки русскоязычных веб-проектов необходимо включать в файл с общими настройками следующие команды:
Код - файл общих настроек |
|
1 |
<?php // Файл общих настроек ... // Вывод заголовка с данными о кодировке страницы header('Content-Type: text/html; charset=windows-1251'); // Настройка локали setlocale(LC_ALL, 'ru_RU.CP1251', 'rus_RUS.CP1251', 'Russian_Russia.1251', 'russian'); // Настройка подключения к базе данных mysql_query('SET names "cp1251"'); ?> |
Как ни странно, но эти три строчки кода значительно повышают портируемость веб-проектов.