UTF-8 весь шлях


1191

Я налаштовую новий сервер і хочу повністю підтримати UTF-8 у своєму веб-додатку. Я раніше це пробував на існуючих серверах, і мені здається, що в кінцевому підсумку потрібно повернутися до ISO-8859-1.

Де саме мені потрібно встановити кодування / схеми? Я усвідомлюю, що для цього мені потрібно налаштувати Apache, MySQL та PHP - чи є якийсь стандартний контрольний список, який я можу дотримуватися, чи можливо усунення неполадок, де трапляються невідповідності?

Це для нового сервера Linux, на якому запущені MySQL 5, PHP, 5 та Apache 2.


8
Ось огляд усіх несправностей кодування, які ви можете зробити: sebastianviereck.de/en/…
Себастьян Вірек


Деякі останні дискусії щодо PHP 7 свідчать про те, що в "офіційно покинутому" становищі 2010 року жодних змін немає ... Є щось більше про "PHP7 та UTF-8"?
Пітер Краус

Ця проблема є загальною. Але рішення для ярликів немає, вам доведеться налаштувати utf-8кожен з них окремо - MySQL 5, PHP 5 АБО Apache 2.
Manish Shrivastava

Відповіді:


1015

Зберігання даних :

  • Вкажіть utf8mb4набір символів для всіх таблиць та текстових стовпців у вашій базі даних. Це змушує MySQL фізично зберігати та отримувати значення, закодовані в UTF-8. Зауважте, що MySQL буде неявно використовувати utf8mb4кодування, якщо utf8mb4_*вказано зіставлення (без явного набору символів).

  • У старих версіях MySQL (<5.5.3), на жаль, ви будете змушені використовувати просто utf8, який підтримує лише підмножину символів Unicode. Мені б хотілося, що я жартую.

Доступ до даних :

  • У коді програми (наприклад, PHP) у будь-якому методі доступу до БД, який ви використовуєте, вам потрібно буде встановити схему з'єднання utf8mb4. Таким чином, MySQL не здійснює перетворення з рідного UTF-8, коли передає дані вашій програмі, і навпаки.

  • Деякі драйвери надають власний механізм налаштування набору символів з'єднання, який одночасно оновлює свій власний внутрішній стан та інформує MySQL про кодування, яке буде використовуватися для з'єднання - зазвичай це кращий підхід. На PHP:

    • Якщо ви використовуєте шар абстракції PDO з PHP ≥ 5.3.6, ви можете вказати charsetв DSN :

      $dbh = new PDO('mysql:charset=utf8mb4');
    • Якщо ви використовуєте mysqli , ви можете зателефонувати set_charset():

      $mysqli->set_charset('utf8mb4');       // object oriented style
      mysqli_set_charset($link, 'utf8mb4');  // procedural style
      
    • Якщо ви застрягли в простому mysql, але у вас працює PHP ≥ 5.2.3, ви можете зателефонувати mysql_set_charset.

  • Якщо драйвер не надає свій власний механізм для установки набору символів з'єднання, можливо , доведеться видати запит , щоб сказати MySQL , як ваш додаток очікує дані про зв'язок повинні бути закодовані: SET NAMES 'utf8mb4'.

  • Те саме, що стосується utf8mb4/ utf8застосовується, як і вище.

Вихід :

  • Якщо ваша програма передає текст іншим системам, їх також потрібно буде повідомити про кодування символів. За допомогою веб-додатків браузер повинен бути поінформований про кодування, в яке надсилаються дані (через заголовки відповідей HTTP або метадані HTML ).

  • У PHP ви можете скористатися параметром default_charsetphp.ini або самостійно оформити Content-Typeзаголовок MIME, що просто більше роботи, але має той же ефект.

  • При кодуванні виводу з використанням json_encode(), додайте JSON_UNESCAPED_UNICODEяк другий параметр.

Вхід :

  • На жаль, ви повинні перевірити кожен отриманий рядок як дійсний UTF-8, перш ніж намагатися зберігати його або використовувати його в будь-якому місці. PHP mb_check_encoding()робить трюк, але ви повинні використовувати його релігійно. Насправді цього не обійтися, оскільки зловмисні клієнти можуть надсилати дані в будь-яку кодування, яку вони хочуть, і я не знайшов хитрості, щоб змусити PHP зробити це для вас надійно.

  • З мого читання поточної специфікації HTML , наступні підпункти більше не потрібні або навіть дійсні для сучасного HTML. Я розумію, що браузери працюватимуть і надсилатимуть дані у наборі символів, визначеному для документа. Однак якщо ви орієнтуєтесь на більш старі версії HTML (XHTML, HTML4 тощо), ці пункти все ще можуть бути корисними:

    • Тільки для HTML перед HTML5 : ви хочете, щоб усі дані, що надсилаються вами браузерам, знаходилися в UTF-8. На жаль, якщо ви йдете на єдиний спосіб надійно зробити це додати accept-charsetатрибут усіх <form>тегів: <form ... accept-charset="UTF-8">.
    • Тільки для HTML перед HTML5 : зауважте, що специфікація HTML W3C говорить, що клієнти "повинні" за замовчуванням надсилати форми назад на сервер у будь-якому коду, який сервер обслуговував, але це, мабуть, лише рекомендація, отже, необхідність бути явною для кожного <form>тег.

Інші міркування щодо кодексу :

  • Очевидно, що всі файли, які ви обслуговуєте (PHP, HTML, JavaScript тощо), повинні бути закодовані у дійсному UTF-8.

  • Вам потрібно переконатися, що кожного разу, коли ви обробляєте рядок UTF-8, ви робите це безпечно. Це, на жаль, важка частина. Ви, ймовірно, захочете широко використовувати розширення PHP mbstring.

  • Вбудовані рядкові операції PHP за замовчуванням не є безпечними для UTF-8. Є деякі речі, які ви можете сміливо робити при звичайних операціях PHP string (наприклад, конкатенація), але для більшості речей слід використовувати еквівалентну mbstringфункцію.

  • Щоб знати, що ви робите (читайте: не псуйте це), вам дійсно потрібно знати UTF-8 і як він працює на найнижчому можливому рівні. Перегляньте будь-яке посилання з utf8.com на корисні ресурси, щоб дізнатися все, що вам потрібно знати.


4
Наскільки я розумію, що якщо вказати порівняння як utf8_ *, воно також автоматично кодує як utf8. Це неправильно?
chazomaticus

49
Я не помиляюсь: COLLATE передбачає набір ХАРАКТЕРІВ. Див., Наприклад, dev.mysql.com/doc/refman/5.0/en/charset-database.html .
chazomaticus

7
Розглянемо додавання прикладів PDO для встановлення набору символів.
Ja͢ck

97
Зауважте, що MySQL не говорить тією ж мовою, що і всі інші. Коли MySQL каже "utf8", це насправді означає "якийсь дивно відсталий варіант UTF-8, який обмежений трьома байтами, бог знає, що смішна причина". Якщо ви дійсно хочете UTF-8, ви повинні сказати MySQL, що ви хочете цю дивну річ, яку MySQL любить називати utf8mb4 . Не заважайте економити на "WTF!"
Р. Мартіньо Фернандес

4
Ця відповідь мені так допомогла, Але я також виявив, що в моєму випадку мені потрібно було додати JSON_UNESCAPED_UNICODE до мого PHP json_encode при передачі результатів запиту БД через ajax.
Petay87

150

Я хотів би додати одне до чудової відповіді chazomaticus :

Не забудьте і тег META (як-от цього, або його HTML4 або XHTML-версію ):

<meta charset="utf-8">

Це здається тривіальним, але IE7 вже давав мені проблеми з цим.

Я все робив правильно; для бази даних, підключення до бази даних та заголовка HTTP-контенту вмісту було встановлено значення UTF-8, і він працював чудово у всіх інших браузерах, але Internet Explorer все ще наполягав на використанні кодексу "західноєвропейський".

Виявилося, що на сторінці відсутній тег META. Додавання, що вирішило проблему.

Редагувати:

Насправді W3C має досить великий розділ, присвячений I18N . У них є ряд статей, пов’язаних із цим питанням - описують HTTP, (X) HTML та CSS сторону речей:

Вони рекомендують використовувати як HTTP-заголовок, так і HTML-тег HTML (або декларацію XML у випадку, коли XHTML слугує XML).


Чи не може бути також можливим вказати комплект у заголовках HTTP? Напевно, потрібен певний параметр config для веб-сервера ...
oliver

2
@oliver: Так, ви можете надіслати його у заголовку HTTP, але краще надіслати його у вмісті, оскільки якщо клієнт збереже файл, він завжди збереже метатег. Заголовок HTTP, ймовірно, просто зникне, якщо браузер не буде достатньо розумним, щоб скопіювати його у метатег у збереженому файлі.

5
Також переконайтеся, що рядок є першим дочірнім елементом заголовка (перед будь-якими елементами Unicode). Після переходу на описаний вище мета-елемент браузер може переосмислити сторінку.
alex

64

Окрім налаштувань default_charsetу php.ini, ви можете надіслати правильну шаблону за допомогою header()свого коду до будь-якого виводу:

header('Content-Type: text/html; charset=utf-8');

Робота з Unicode в PHP проста, якщо ви усвідомлюєте, що більшість функцій рядків не працюють з Unicode, а деякі можуть обробляти рядки повністю . PHP вважає, що "символи" мають 1 байт. Іноді це нормально (наприклад, explode()тільки шукає послідовність байтів і використовує її як роздільник - тому не має значення, які саме символи ви шукаєте). Але в інші часи, коли функція фактично призначена для роботи над символами , PHP не має уявлення про те, що у вашому тексті є багатобайтові символи, знайдені в Unicode.

Хороша бібліотека для перевірки - phputf8 . Це переписує всі "погані" функції, щоб ви могли безпечно працювати над рядками UTF8. Є такі розширення, як розширення mbstring, які намагаються зробити це і для вас, але я вважаю за краще використовувати бібліотеку, оскільки вона більш портативна (але я пишу продукти масового ринку, тому це важливо для мене). Але phputf8 у будь-якому разі може використовувати mbstring за кадром, щоб збільшити продуктивність.


Встановіть налаштування перевантаження в php.ini. Це допомагає при використанні багатобайтових рядків.
Ентоні Рутлідж

32

Я знайшов проблему з тим, хто використовує PDO, і відповідь полягає в тому, щоб використовувати це для рядка з'єднання PDO:

$pdo = new PDO(
    'mysql:host=mysql.example.com;dbname=example_db',
    "username",
    "password",
    array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"));

Сайт, з якого я взяв це, не працює, але мені вдалося отримати його за допомогою кешу Google, на щастя.


1
Шукаючи це трохи далі, це потрібно лише для версій PHP до 5.3.6. Дивіться також: http://stackoverflow.com/a/4361485/2286722 (хоча вони використовують окремий $dbh->exec("set names utf8");; я віддаю перевагу представленому тут методу). Btw. Є також аналогічна примітка до цього, як коментар у посібнику PHP: php.net/manual/en/pdo.construct.php#96325 .
Marten Koetsier


24

У моєму випадку я використовував mb_split, який використовує регулярний вираз. Тому я також повинен був вручну переконатися, що кодування регулярного виразу було utf-8, виконуючи цеmb_regex_encoding('UTF-8');

Як бічну зауваження, я також виявив, запустивши, mb_internal_encoding()що внутрішнє кодування не utf-8, і змінив це, запустивши mb_internal_encoding("UTF-8");.


22

Перш за все, якщо ви знаходитесь у <5.3PHP, то ні. У вас є маса проблем, які потрібно вирішити.

Я здивований, що ніхто не згадав про бібліотеку intl , ту, яка має гарну підтримку unicode , графем , рядкових операцій , локалізації та багато іншого, дивіться нижче.

Я наведу деякі відомості про підтримку Unicode в PHP на слайдах Елізабет Сміт на PHPBenelux'14

INTL

Добре:

  • Обмотка навколо бібліотеки ICU
  • Стандартизовані локалі, встановити локаль для кожного сценарію
  • Форматування чисел
  • Форматування валюти
  • Форматування повідомлень (замінює gettext)
  • Календарі, дати, часовий пояс і час
  • Транслітератор
  • Spoofchecker
  • Пакети ресурсів
  • Перетворювачі
  • Підтримка IDN
  • Графеми
  • Збірка
  • Ітератори

Погано:

  • Не підтримує zend_multibite
  • Не підтримує вхідне перетворення HTTP
  • Не підтримує функцію перевантаження

mb_string

  • Вмикає підтримку zend_multibyte
  • Підтримує прозоре кодування HTTP вводу / виводу
  • Надає деякі обгортки для функціональності, такі як strtoupper

ICONV

  • Основна для перетворення діаграм
  • Обробник вихідного буфера
  • функція кодування mime
  • конверсія
  • деякі помічники струн (len, substr, strpos, strrpos)
  • Потоковий фільтр stream_filter_append($fp, 'convert.iconv.ISO-2022-JP/EUC-JP')

БАНКИ

  • mysql: Набір таблиць і порівняння на таблицях та підключеннях (не порівняння). Також не використовуйте mysql - msqli або PDO
  • postgresql: pg_set_client_encoding
  • sqlite (3): переконайтеся, що він був складений за допомогою підтримки unicode та intl

Деякі інші готчі

  • Ви не можете використовувати імена файлів Unicode з PHP та Windows, якщо не використовуєте розширення 3-ї частини.
  • Відправте все в ASCII, якщо ви використовуєте файли exec, proc_open та інші виклики командного рядка
  • Звичайний текст - це не звичайний текст, у файлах - кодування
  • Ви можете конвертувати файли на ходу за допомогою фільтра iconv

Я оновлю цю відповідь у випадку, якщо щось змінить додані функції тощо.


2
Так правильно. Mysqli та PDO можуть використовувати рідні драйвери. Також вони можуть використовувати драйвер mysqlnd, якщо ви будете компілювати php з --with-mysqli=mysqlnd --with-pdo-mysql=mysqlndпараметрами.
Олександр Янчарук

14

Єдине, що я хотів би додати до цих дивовижних відповідей, це наголосити на збереженні своїх файлів у кодування utf8, я помітив, що браузери приймають це властивість за допомогою налаштування utf8 як кодування коду. Будь-який гідний текстовий редактор покаже вам це, наприклад, у Блокноті ++ є опція меню для об'єднання файлів, він показує вам поточне кодування та дозволяє змінити його. Для всіх моїх файлів php я використовую utf8 без BOM.

Колись тому мені хтось попросив додати підтримку utf8 для програми php / mysql, розробленої кимось іншим, я помітив, що всі файли закодовані в ANSI, тому мені довелося використовувати ICONV для перетворення всіх файлів, зміни таблиць бази даних, щоб використовувати utf8 charset та utf8_general_ci порівнюйте, додайте "SET NAMES utf8" до рівня абстракції бази даних після з'єднання (якщо ви використовуєте 5.3.6 або раніше, ви повинні використовувати charset = utf8 у рядку з'єднання) та змінити рядкові функції, щоб використовувати багатобайтовий php рядкові функції еквівалентні


13

Нещодавно я виявив, що використання strtolower()може спричинити проблеми, коли дані обрізаються після спеціального символу.

Рішення було використовувати

mb_strtolower($string, 'UTF-8');

mb_ використовує MultiByte. Він підтримує більше символів, але загалом трохи повільніше.


9

Я щойно пережив ту саму проблему і знайшов хороше рішення в керівництві PHP.

Я змінив все кодування свого файлу на UTF8, а потім кодування за замовчуванням у моєму з'єднанні. Це вирішило всі проблеми.

if (!$mysqli->set_charset("utf8")) {
    printf("Error loading character set utf8: %s\n", $mysqli->error);
} else {
   printf("Current character set: %s\n", $mysqli->character_set_name());
}

Переглянути джерело


2
Я витратив годину, намагаючись вирішити проблему кодування на сторінці, над якою працюю, і, як правило, досить добре розгадую речі. Я завжди звертаюся до цієї сторінки, і ваша відповідь мені дуже допомогла. Отримав мою пропозицію. У моєму випадку set_charset('utf8mb4')це не працювало, але >set_charset("utf8")було, що насправді не було показано в інших відповідях.
Funk Forty Niner

@FunkFortyNiner Обережно: set_charset("utf8")може працювати, але поводитиметься по-різному (див. Зауваження щодо різниці між utf8та utf8mb4історією версії mysql). Використовуйте, utf8 якщо вам потрібно І ТІЛЬКИ, якщо ви знаєте, що робите !
Мартін Хеннінгс

5 зіркових рішень, я читав текстовий файл за рядком і отримував? для кожного символу, тоді я зробив save-as, замість ansi, використовував utf8. Дякую.
Атеф Фарук

8

У PHP вам потрібно буде використовувати багатобайтові функції або ввімкнути mbstring.func_overload . Таким чином, такі речі, як strlen, будуть працювати, якщо у вас є символи, які займають більше одного байта.

Вам також потрібно буде визначити набір символів ваших відповідей. Ви можете використовувати AddDefaultCharset, як зазначено вище, або написати PHP-код, який повертає заголовок. (Або ви можете додати тег META до своїх документів HTML.)


Чудова порада щодо налаштування func_overload - дозволяє мінімально змінювати існуючий код.
Саймон Схід

4
Будьте обережні - деякий код може насправді спиратися на характер одного байта на символ стандартних рядкових функцій.
JW.

Важливо зауважити, що функція mbstring.func_overload припиняється з PHP 7.2 через проблеми, зазначені в коментарі @ JW вище. Тож найкраща порада: Так, ви обов'язково повинні використовувати функції mbstring, але не використовуйте функцію перевантаження, щоб стандартні функції працювали як багатобайтові.
Сімба

6

Підтримка Unicode в PHP все ще величезна безлад. Незважаючи на те, що він здатний перетворити рядок ISO8859 (який він використовує внутрішньо) в utf8, але він не має можливості працювати з Unicode рядками, тобто це означає, що всі функції обробки рядків будуть маніпулювати та пошкоджувати ваші рядки. Тож вам доведеться або використовувати окрему бібліотеку для належної підтримки utf8, або самостійно переписати всі функції обробки рядків.

Найпростіша частина полягає лише у визначенні діаграми в заголовках HTTP та в базі даних тощо, але жодне з цього не має значення, якщо ваш PHP-код не виводить дійсний UTF8. Це важка частина, і PHP вам практично не допомагає. (Я думаю, що PHP6 повинен виправити найгірше, але це ще деякий час)


6

Якщо ви хочете, щоб сервер MySQL вирішив набір символів, а не PHP як клієнт (стара поведінка; на мою думку, бажано), спробуйте додати його skip-character-set-client-handshakeдо my.cnf, під [mysqld]та перезапустити mysql.

Це може спричинити проблеми, якщо ви використовуєте що-небудь, крім UTF8.


5

Верхня відповідь відмінна. Ось що мені довелося робити при звичайній установці debian / php / mysql:

// storage
// debian. apparently already utf-8

// retrieval
// the mysql database was stored in utf-8, 
// but apparently php was requesting iso. this worked: 
// ***notice "utf8", without dash, this is a mysql encoding***
mysql_set_charset('utf8');

// delivery
// php.ini did not have a default charset, 
// (it was commented out, shared host) and
// no http encoding was specified in the apache headers.
// this made apache send out a utf-8 header
// (and perhaps made php actually send out utf-8)
// ***notice "utf-8", with dash, this is a php encoding***
ini_set('default_charset','utf-8');

// submission
// this worked in all major browsers once apache
// was sending out the utf-8 header. i didnt add
// the accept-charset attribute.

// processing
// changed a few commands in php, like substr,
// to mb_substr

це було все!


1

якщо ви хочете вирішити mysql, у мене були схожі проблеми з двома своїми проектами після міграції сервера. Після пошуку та спробу багатьох рішень я зіткнувся з цим / нічого, перш ніж цей працював):

mysqli_set_charset($con,"utf8");

Після додавання цього рядка до мого конфігураційного файлу все працює добре!

Я знайшов це рішення https://www.w3schools.com/PHP/func_mysqli_set_charset.asp, коли я шукав вирішити вставку з html запиту

Щасти!


1

Просто примітка:

Ви зіткнулися з проблемою своїх не латинських символів , показуючи , як ?????????ви задали питання, і він отримав закритий з посиланням на цей канонічний питання, ви перепробували всі , і незалежно від того , що ви робите , ви все ще отримуєте ??????????від MySQL.

Це здебільшого тому, що ви протестуєте свої старі дані, які були вставлені в базу даних за допомогою неправильної діаграми і перетворені та збережені фактично символами знака питання ?. Це означає, що ви втратили свій оригінальний текст назавжди, і незалежно від того, що ви намагаєтеся, ви отримаєте ???????.

повторне застосування того, що ви дізналися з відповідей на це запитання на нових даних, може вирішити вашу проблему.


0

У мене виникло це питання під час відображення таблиць. Я просто ставлю це до кожної змінної вихідної ехографії:

<td><?php echo utf8_encode ($Local) ?></td>
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.