UTF-8 przez całą drogę

1191

Konfiguruję nowy serwer i chcę w pełni obsługiwać UTF-8 w mojej aplikacji internetowej. Próbowałem tego w przeszłości na istniejących serwerach i zawsze wydaje mi się, że muszę wrócić do ISO-8859-1.

Gdzie dokładnie muszę ustawić kodowanie / zestawy znaków? Zdaję sobie sprawę, że muszę skonfigurować Apache, MySQL i PHP, aby to zrobić - czy istnieje jakaś standardowa lista kontrolna, którą mogę śledzić, a może rozwiązywać problemy w przypadku wystąpienia niezgodności?

Dotyczy to nowego serwera Linux z systemem MySQL 5, PHP, 5 i Apache 2.

mercutio
źródło
8
Oto przegląd wszystkich możliwych błędów kodowania: sebastianviereck.de/en/…
Sebastian Viereck
13
Oto wprowadzenie do kodowania w ogóle, a kodowania w PHP w szczególności: Co absolutnie, pozytywnie musi wiedzieć każdy programista o kodowaniu i
zestawach
Ostatnie dyskusje na temat PHP 7 wskazują, że nie ma żadnych zmian w „oficjalnie porzuconej” pozycji w 2010 roku… Jest coś więcej o „PHP7 i UTF-8”?
Peter Krauss,
Ten problem jest powszechny. Ale nie ma rozwiązania skrótu, będziesz musiał skonfigurować utf-8osobno dla każdego z nich - MySQL 5, PHP 5 OR Apache 2.
Manish Shrivastava

Odpowiedzi:

1015

Przechowywanie danych :

  • Określ utf8mb4zestaw znaków dla wszystkich tabel i kolumn tekstowych w bazie danych. To sprawia, że ​​MySQL fizycznie przechowuje i pobiera wartości zakodowane natywnie w UTF-8. Zauważ, że MySQL będzie domyślnie używał utf8mb4kodowania, jeśli określono utf8mb4_*sortowanie (bez żadnego jawnego zestawu znaków).

  • W starszych wersjach MySQL (<5.5.3) niestety będziesz zmuszony używać po prostu utf8, który obsługuje tylko podzbiór znaków Unicode. Chciałbym żartować.

Dostęp do danych :

  • W kodzie aplikacji (np. PHP), niezależnie od używanej metody dostępu do bazy danych DB, musisz ustawić zestaw znaków połączenia na utf8mb4. W ten sposób MySQL nie dokonuje konwersji ze swojego natywnego UTF-8, gdy przekazuje dane do twojej aplikacji i odwrotnie.

  • Niektóre sterowniki zapewniają własny mechanizm konfigurowania zestawu znaków połączenia, który zarówno aktualizuje swój wewnętrzny stan, jak i informuje MySQL o kodowaniu, które ma być użyte w połączeniu - jest to zazwyczaj preferowane podejście. W PHP:

    • Jeśli używasz warstwy abstrakcji PDO z PHP ≥ 5.3.6, możesz określić charsetw DSN :

      $dbh = new PDO('mysql:charset=utf8mb4');
    • Jeśli używasz mysqli , możesz zadzwonić set_charset():

      $mysqli->set_charset('utf8mb4');       // object oriented style
      mysqli_set_charset($link, 'utf8mb4');  // procedural style
    • Jeśli utknąłeś z zwykłym mysql, ale akurat używasz PHP ≥ 5.2.3, możesz zadzwonić mysql_set_charset.

  • Jeśli kierowca nie przewiduje własny mechanizm ustawiania zestawu znaków połączenia, być może trzeba będzie wydać zapytanie do powiedzenia MySQL jak aplikacja oczekuje dane dotyczące połączenia mają być zakodowane: SET NAMES 'utf8mb4'.

  • Ta sama uwaga dotycząca utf8mb4/ utf8obowiązuje jak powyżej.

Wyjście :

  • Jeśli Twoja aplikacja przesyła tekst do innych systemów, będą one również musiały zostać poinformowane o kodowaniu znaków. W przypadku aplikacji internetowych przeglądarka musi być informowana o kodowaniu, w którym dane są wysyłane (za pomocą nagłówków odpowiedzi HTTP lub metadanych HTML ).

  • W PHP możesz użyć default_charsetopcji php.ini lub ręcznie wydać Content-Typenagłówek MIME, co jest po prostu więcej pracy, ale ma ten sam efekt.

  • Podczas kodowania danych wyjściowych za pomocą json_encode()dodaj JSON_UNESCAPED_UNICODEjako drugi parametr.

Wejście :

  • Niestety, powinieneś zweryfikować każdy otrzymany ciąg jako poprawny UTF-8, zanim spróbujesz go zapisać lub użyć w dowolnym miejscu. PHP mb_check_encoding()załatwia sprawę, ale musisz używać jej religijnie. Naprawdę nie ma takiej możliwości, ponieważ złośliwi klienci mogą przesyłać dane w dowolnym pożądanym przez siebie kodowaniu, a ja nie znalazłem sposobu, aby PHP rzuciło się za ciebie niezawodnie.

  • Z mojej lektury aktualnej specyfikacji HTML , następujące podpunkty nie są już potrzebne, a nawet aktualne dla współczesnego HTML. Rozumiem, że przeglądarki będą współpracować i przesyłać dane w zestawie znaków określonym dla dokumentu. Jeśli jednak kierujesz reklamy na starsze wersje HTML (XHTML, HTML4 itp.), Te punkty mogą być nadal przydatne:

    • Tylko w przypadku HTML wcześniejszego niż HTML5 : chcesz, aby wszystkie dane przesyłane do Ciebie przez przeglądarki znajdowały się w UTF-8. Niestety, jeśli przejdziesz przez jedyny sposób, aby niezawodnie zrobić to dodać accept-charsetatrybut do wszystkich <form>tagów: <form ... accept-charset="UTF-8">.
    • Tylko w przypadku HTML przed HTML5 : zauważ, że specyfikacja W3C HTML mówi, że klienci „powinni” domyślnie wysyłać formularze z powrotem do serwera w dowolnym zestawie znaków obsługiwanym przez serwer, ale najwyraźniej jest to tylko zalecenie, stąd potrzeba jawności na każdym <form>etykietka.

Inne uwagi dotyczące kodu :

  • Oczywiście wszystkie pliki, które będziesz obsługiwać (PHP, HTML, JavaScript itp.), Powinny być zakodowane w prawidłowym UTF-8.

  • Musisz upewnić się, że za każdym razem, gdy przetwarzasz ciąg UTF-8, robisz to bezpiecznie. To niestety trudna część. Prawdopodobnie będziesz chciał skorzystać z mbstringrozszerzenia PHP .

  • Wbudowane operacje PHP na łańcuchach nie są domyślnie bezpieczne dla UTF-8. Są pewne rzeczy, które możesz bezpiecznie zrobić przy pomocy normalnych operacji na łańcuchach PHP (np. Konkatenacja), ale dla większości rzeczy powinieneś użyć równoważnej mbstringfunkcji.

  • Aby wiedzieć, co robisz (czytaj: nie zepsuć), naprawdę musisz znać UTF-8 i jak działa na najniższym możliwym poziomie. Sprawdź dowolne linki z utf8.com, aby znaleźć dobre zasoby, aby dowiedzieć się wszystkiego, co musisz wiedzieć.

chazomaticus
źródło
4
Rozumiem, że jeśli określisz sortowanie jako utf8_ *, automatycznie koduje również jako utf8. Czy to źle?
chazomaticus
49
Nie mylę się: COLLATE oznacza ZESTAW ZNAKÓW. Zobacz np . Dev.mysql.com/doc/refman/5.0/en/charset-database.html .
chazomaticus
7
Rozważ dodanie przykładów PDO również do ustawiania zestawu znaków.
Ja͢ck
97
Zauważ, że MySQL nie mówi tym samym językiem, co wszyscy inni. Kiedy MySQL mówi „utf8”, tak naprawdę oznacza „jakiś dziwnie opóźniony wariant UTF-8, który jest ograniczony do trzech bajtów, bo Bóg wie, jaki absurdalny powód”. Jeśli naprawdę chcesz UTF-8, powinieneś powiedzieć MySQL, że chcesz tej dziwnej rzeczy, którą MySQL lubi nazywać utf8mb4 . Nie przejmuj się oszczędzaniem na „WTF!”.
R. Martinho Fernandes,
4
Ta odpowiedź pomogła mi tak bardzo, ale odkryłem również, że w moim przypadku muszę dodać JSON_UNESCAPED_UNICODE do mojego kodu PHP json_enc podczas przekazywania wyników zapytania DB z powrotem za pośrednictwem ajax.
Petay87
150

Chciałbym dodać jedną rzecz do doskonałej odpowiedzi chazomaticus :

Nie zapomnij o znaczniku META (takim jak ten lub jego wersji HTML4 lub XHTML ):

<meta charset="utf-8">

Wydaje się to trywialne, ale IE7 już dawało mi z tym problemy.

Zrobiłem wszystko dobrze; baza danych, połączenie z bazą danych i nagłówek HTTP Content-Type zostały ustawione na UTF-8 i działało dobrze we wszystkich innych przeglądarkach, ale Internet Explorer nadal nalegał na stosowanie kodowania „zachodnioeuropejskiego”.

Okazało się, że na stronie brakuje tagu META. Dodanie to rozwiązało problem.

Edytować:

W3C faktycznie ma dość dużą sekcję poświęconą I18N . Mają wiele artykułów związanych z tym problemem - opisujących strony HTTP, (X) HTML i CSS:

Zalecają używanie zarówno nagłówka HTTP, jak i metatagu HTML (lub deklaracji XML w przypadku XHTML podanego jako XML).

mercator
źródło
Czy nie powinno być również możliwe określenie zestawu znaków w nagłówkach HTTP? Prawdopodobnie potrzebuje jakiejś opcji konfiguracji dla serwera WWW ...
oliver
2
@oliver: Tak, możesz wysłać go w nagłówku HTTP, ale lepiej wysłać go w treści, ponieważ jeśli klient zapisze plik, zawsze zapisze metatag. Nagłówek HTTP prawdopodobnie po prostu zniknie, chyba że przeglądarka jest wystarczająco inteligentna, aby skopiować go do metatagu w zapisanym pliku.
5
Upewnij się również, że linia jest pierwszym dzieckiem elementu head (przed jakimikolwiek elementami Unicode). Przeglądarka może ponownie zinterpretować stronę po dotknięciu tego elementu meta opisanego powyżej.
alex
64

Oprócz ustawienia default_charsetw php.ini, możesz wysłać prawidłowy zestaw znaków za pomocą header()z twojego kodu, przed jakimkolwiek wyjściem:

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

Praca z Unicode w PHP jest łatwa, pod warunkiem, że zdajesz sobie sprawę, że większość funkcji łańcuchowych nie działa z Unicode, a niektóre mogą całkowicie zakłócać łańcuchy . PHP uważa, że ​​„znaki” mają długość 1 bajta. Czasami jest to w porządku (na przykład explode()szuka tylko sekwencji bajtów i używa jej jako separatora - więc nie ma znaczenia, jakich rzeczywistych znaków szukasz). Ale innym razem, kiedy funkcja jest rzeczywiście zaprojektowana do pracy na znakach , PHP nie ma pojęcia, że ​​twój tekst zawiera znaki wielobajtowe znalezione w Unicode.

Dobrą biblioteką do sprawdzenia jest phputf8 . Spowoduje to przepisanie wszystkich „złych” funkcji, abyś mógł bezpiecznie pracować na łańcuchach UTF8. Istnieją rozszerzenia, takie jak rozszerzenie mbstring, które również próbują to zrobić dla Ciebie, ale wolę korzystać z biblioteki, ponieważ jest ona bardziej przenośna (ale piszę produkty na rynek masowy, więc to jest dla mnie ważne). Ale phputf8 może i tak używać mbstringa za kulisami, aby zwiększyć wydajność.

chroder
źródło
Ustaw ustawienie przeciążenia w php.ini. Pomaga przy użyciu ciągów wielobajtowych.
Anthony Rutledge,
32

Znalazłem problem z osobą używającą PDO i odpowiedzią było użycie tego dla ciągu połączenia PDO:

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

Witryna, z której to wziąłem, nie działa, ale na szczęście udało mi się ją uzyskać przy użyciu pamięci podręcznej Google.

Jim W.
źródło
1
Szukając tego nieco dalej, jest to konieczne tylko w wersjach PHP wcześniejszych niż 5.3.6. Zobacz także: http://stackoverflow.com/a/4361485/2286722 (chociaż używają osobnego $dbh->exec("set names utf8");; wolę przedstawioną tutaj metodę). Btw. jest również podobna uwaga na ten temat jako komentarz w podręczniku PHP: php.net/manual/en/pdo.construct.php#96325 .
Marten Koetsier,
24

W moim przypadku mb_splitkorzystałem z wyrażeń regularnych. Dlatego musiałem też ręcznie upewnić się, że kodowanie wyrażenia regularnego to utf-8mb_regex_encoding('UTF-8');

Na marginesie, odkryłem również przez uruchomienie, mb_internal_encoding()że wewnętrzne kodowanie nie było utf-8, i zmieniłem to przez uruchomienie mb_internal_encoding("UTF-8");.

JDelage
źródło
22

Przede wszystkim, jeśli jesteś w <5.3PHP, to nie. Masz mnóstwo problemów do rozwiązania.

Dziwi mnie, że żadna nie wspomniała o bibliotece intl , która ma dobre wsparcie dla Unicode , grafemów , operacji na łańcuchach znaków , lokalizacji i wielu innych, patrz poniżej.

Przytoczę kilka informacji o obsłudze Unicode w PHP przez slajdy Elizabeth Smith na PHPBenelux'14

INTL

Dobrze:

  • Otocz się biblioteką ICU
  • Standaryzowane ustawienia regionalne, ustawianie ustawień regionalnych dla skryptu
  • Formatowanie liczb
  • Formatowanie walut
  • Formatowanie wiadomości (zastępuje gettext)
  • Kalendarze, daty, strefa czasowa i czas
  • Transliterator
  • Spoofchecker
  • Pakiety zasobów
  • Konwertery
  • Obsługa IDN
  • Graphemes
  • Porównanie
  • Iteratory

Zły:

  • Nie obsługuje zend_multibite
  • Nie obsługuje konwersji danych wyjściowych HTTP
  • Nie obsługuje przeciążania funkcji

mb_string

  • Włącza obsługę zend_multibyte
  • Obsługuje przezroczyste kodowanie wejścia / wyjścia HTTP
  • Zapewnia niektóre opakowania dla funkcji funkcyjnych, takie jak strtoupper

ICONV

  • Podstawowy do konwersji zestawu znaków
  • Moduł obsługi bufora wyjściowego
  • funkcjonalność kodowania MIME
  • konwersja
  • niektóre ciągi pomocnicze (len, substr, strpos, strrpos)
  • Filtr strumieniowy stream_filter_append($fp, 'convert.iconv.ISO-2022-JP/EUC-JP')

BAZY DANYCH

  • mysql: Zestaw znaków i zestawianie w tabelach i przy połączeniu (nie zestawienie). Nie używaj też mysql - msqli lub PDO
  • postgresql: pg_set_client_encoding
  • sqlite (3): Upewnij się, że został skompilowany z obsługą Unicode i Intl

Niektóre inne Gotchas

  • Nie możesz używać nazw plików Unicode w PHP i Windows, chyba że użyjesz rozszerzenia trzeciej części.
  • Wyślij wszystko w ASCII, jeśli używasz exec, proc_open i innych wywołań z wiersza poleceń
  • Zwykły tekst nie jest zwykłym tekstem, pliki mają kodowanie
  • Możesz konwertować pliki w locie dzięki filtrowi iconv

Zaktualizuję tę odpowiedź na wypadek, gdyby coś zmieniło dodane funkcje i tak dalej.

Jimmy Kane
źródło
2
Tak, racja. Mysqli i PDO mogą używać swoich natywnych sterowników. Mogą także użyć sterownika mysqlnd, jeśli skompilujesz php z --with-mysqli=mysqlnd --with-pdo-mysql=mysqlndopcjami.
Alexander Yancharuk
14

Jedyne, co chciałbym dodać do tych niesamowitych odpowiedzi, to podkreślić nacisk na zapisywanie plików w kodowaniu utf8, zauważyłem, że przeglądarki akceptują tę właściwość zamiast ustawiania utf8 jako kodowania kodu. Każdy przyzwoity edytor tekstu to pokaże, na przykład Notepad ++ ma opcję menu do kodowania plików, pokazuje bieżące kodowanie i umożliwia jego zmianę. Do wszystkich moich plików php używam utf8 bez BOM.

Jakiś czas temu ktoś poprosił mnie o dodanie obsługi utf8 dla aplikacji php / mysql zaprojektowanej przez kogoś innego, zauważyłem, że wszystkie pliki zostały zakodowane w ANSI, więc musiałem użyć ICONV do konwersji wszystkich plików, zmienić tabele bazy danych, aby użyć utf8 charset i utf8_general_ci zestawiają, dodaj 'SET NAMES utf8' do warstwy abstrakcji bazy danych po połączeniu (jeśli używasz 5.3.6 lub wcześniejszej wersji, w przeciwnym razie musisz użyć charset = utf8 w łańcuchu połączenia) i zmień funkcje łańcucha, aby użyć wielobajtowego php ekwiwalent funkcji łańcuchowych.

Puerto AGP
źródło
13

Niedawno odkryłem, że używanie strtolower()może powodować problemy, w których dane są obcinane po znaku specjalnym.

Rozwiązaniem było użyć

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

mb_ używa MultiByte. Obsługuje więcej postaci, ale ogólnie jest nieco wolniejszy.

Miguel Stevens
źródło
9

Właśnie przejrzałem ten sam problem i znalazłem dobre rozwiązanie w instrukcjach PHP.

Zmieniłem kodowanie wszystkich plików na UTF8, a następnie domyślne kodowanie w moim połączeniu. To rozwiązało wszystkie problemy.

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());
}

Pokaż źródło

Abdul Sadik Yalcin
źródło
2
Spędziłem godzinę próbując znaleźć problem z kodowaniem na stronie, nad którą pracuję, i zazwyczaj jestem całkiem dobry w rozwiązywaniu problemów. Zawsze odwiedzam tę stronę, a twoja odpowiedź bardzo mi pomogła. Mam moją opinię. W moim przypadku set_charset('utf8mb4')nie działało, ale działało >set_charset("utf8")i nie zostało to pokazane w innych odpowiedziach.
Funk Forty Niner
@FunkFortyNiner Uwaga: set_charset("utf8")mogą działać, ale będzie się zachowywał inaczej (patrz uwagi na temat różnicy między utf8i utf8mb4oraz historii wersji MySQL). Użyj, utf8 jeśli musisz TYLKO, jeśli wiesz, co robisz !
Martin Hennings
Rozwiązanie 5 gwiazdek, czytałem plik tekstowy linia po linii i otrzymuję? dla każdej postaci zapisałem, ponieważ zamiast ansi użyłem utf8. dzięki.
Atef Farouk
8

W PHP musisz albo użyć funkcji wielobajtowej , albo włączyć mbstring.func_overload . W ten sposób rzeczy takie jak strlen będą działać, jeśli masz znaki, które zajmują więcej niż jeden bajt.

Musisz także zidentyfikować zestaw znaków swoich odpowiedzi. Możesz użyć AddDefaultCharset, jak wyżej, lub napisać kod PHP, który zwraca nagłówek. (Lub możesz dodać tag META do swoich dokumentów HTML.)

JW.
źródło
Świetna wskazówka na temat ustawienia func_overload - pozwala na minimalną modyfikację istniejącego kodu.
Simon East
4
Bądź ostrożny - niektóre kody mogą w rzeczywistości polegać na standardzie funkcji łańcuchowych na jeden bajt na znak.
JW.
Ważne jest, aby pamiętać, że funkcja mbstring.func_overload jest przestarzała od wersji PHP 7.2 z powodu problemów odnotowanych w komentarzu @ JW powyżej. Tak więc najlepsza rada: Tak, zdecydowanie powinieneś użyć funkcji mbstring, ale nie używaj funkcji przeciążenia, aby standardowe funkcje działały jako wielobajtowe.
Simba,
6

Obsługa Unicode w PHP jest nadal ogromnym bałaganem. Chociaż jest w stanie przekonwertować ciąg ISO8859 (którego używa wewnętrznie) na utf8, brakuje mu możliwości pracy z ciągami Unicode natywnie, co oznacza, że ​​wszystkie funkcje przetwarzania łańcucha będą mangować i uszkadzać twoje ciągi. Musisz więc albo użyć oddzielnej biblioteki, aby zapewnić odpowiednią obsługę utf8, albo samodzielnie przepisać wszystkie funkcje obsługi ciągów.

Najłatwiejszą częścią jest po prostu określenie zestawu znaków w nagłówkach HTTP i bazie danych, ale nic z tego nie ma znaczenia, jeśli kod PHP nie wyświetla prawidłowego kodu UTF8. To jest najtrudniejsza część, a PHP nie daje praktycznie żadnej pomocy. (Myślę, że PHP6 powinien naprawić najgorsze z tego, ale wciąż jest trochę czasu)

jalf
źródło
6

Jeśli chcesz, aby serwer MySQL decydował o zestawie znaków, a nie PHP jako klient (stare zachowanie; moim zdaniem preferowane), spróbuj dodać skip-character-set-client-handshakedo swojego my.cnf, poniżej [mysqld]i uruchom ponownie mysql.

Może to powodować problemy, jeśli używasz czegokolwiek innego niż UTF8.

Budimir Grom
źródło
5

Najlepsza odpowiedź jest doskonała. Oto, co musiałem zrobić podczas regularnej instalacji 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

to było wszystko !

pospolity
źródło
1

jeśli chcesz rozwiązania mysql, po migracji serwera miałem podobne problemy z 2 moimi projektami. Po przeszukaniu i wypróbowaniu wielu rozwiązań natknąłem się na ten / nic, zanim ten zadziałał):

mysqli_set_charset($con,"utf8");

Po dodaniu tej linii do mojego pliku konfiguracyjnego wszystko działa dobrze!

Znalazłem to rozwiązanie https://www.w3schools.com/PHP/func_mysqli_set_charset.asp, kiedy szukałem rozwiązania wstawki z zapytania HTML

powodzenia!

castro_pereira
źródło
1

Tylko uwaga:

Stoją problemu swoimi niełacińskie znaków pokazuje jak ?????????, ty zadał pytanie, a on został zamknięty z odniesieniem do tej kanonicznej pytanie, próbowałem wszystkiego i nie ważne co robisz jeszcze dostać ??????????odMySQL .

Wynika to głównie z tego, że testujesz swoje stare dane, które zostały wstawione do bazy danych przy użyciu niewłaściwego zestawu znaków i zostały przekonwertowane i zapisane w postaci znaków znaku zapytania ?. Co oznacza, że ​​na zawsze utraciłeś oryginalny tekst i bez względu na to, co spróbujesz, otrzymasz??????? .

ponowne zastosowanie tego, czego nauczyłeś się z odpowiedzi na to pytanie, na świeże dane, może rozwiązać Twój problem.

Księgowy م
źródło
0

Miałem ten problem podczas wyświetlania tabel. Po prostu umieszczam to na każdej zmiennej wyjściowej echa:

<td><?php echo utf8_encode ($Local) ?></td>
Joao Fonseca
źródło