UTF-8 terus berlanjut

1191

Saya sedang menyiapkan server baru dan ingin mendukung UTF-8 sepenuhnya dalam aplikasi web saya. Saya telah mencoba ini di masa lalu pada server yang ada dan sepertinya selalu harus kembali ke ISO-8859-1.

Di mana tepatnya saya perlu mengatur pengkodean / rangkaian karakter? Saya sadar bahwa saya perlu mengkonfigurasi Apache, MySQL, dan PHP untuk melakukan ini - apakah ada beberapa daftar periksa standar yang dapat saya ikuti, atau mungkin memecahkan masalah di mana ketidakcocokan terjadi?

Ini untuk server Linux baru, yang menjalankan MySQL 5, PHP, 5 dan Apache 2.

mercutio
sumber
8
Berikut ini adalah ikhtisar tentang semua kesalahan penyandian yang dapat Anda buat: sebastianviereck.de/en/…
Sebastian Viereck
13
Berikut ini adalah pengantar untuk pengkodean secara umum dan pengkodean dalam PHP khususnya: Apa yang Setiap Programmer Harus Sepenuhnya, Sepenuhnya Penting Untuk Diketahui Tentang Pengkodean Dan Kumpulan Karakter Untuk Bekerja Dengan Teks
deceze
Beberapa diskusi terbaru tentang PHP 7 menunjukkan bahwa tidak ada perubahan pada posisi "secara resmi ditinggalkan" pada tahun 2010 ... Ada sesuatu yang lebih tentang "PHP7 dan UTF-8"?
Peter Krauss
Masalah ini biasa terjadi. Tetapi tidak ada solusi pintas, Anda harus mengatur utf-8untuk masing-masing secara terpisah - MySQL 5, PHP 5 ATAU Apache 2.
Manish Shrivastava

Jawaban:

1016

Penyimpanan Data :

  • Tentukan utf8mb4karakter yang ditetapkan pada semua tabel dan kolom teks dalam database Anda. Ini membuat MySQL secara fisik menyimpan dan mengambil nilai yang disandikan secara asli di UTF-8. Perhatikan bahwa MySQL secara implisit akan menggunakan utf8mb4penyandian jika suatu utf8mb4_*collation ditentukan (tanpa set karakter eksplisit).

  • Dalam versi MySQL yang lebih lama (<5.5.3), Anda sayangnya terpaksa menggunakan secara sederhana utf8, yang hanya mendukung subset karakter Unicode. Saya berharap saya bercanda.

Akses Data :

  • Dalam kode aplikasi Anda (mis. PHP), dalam metode akses DB apa pun yang Anda gunakan, Anda harus mengatur charset koneksi utf8mb4. Dengan cara ini, MySQL tidak melakukan konversi dari UTF-8 asalnya ketika menyerahkan data ke aplikasi Anda dan sebaliknya.

  • Beberapa driver menyediakan mekanisme mereka sendiri untuk mengonfigurasi set karakter koneksi, yang keduanya memperbarui keadaan internal sendiri dan menginformasikan MySQL tentang pengkodean yang akan digunakan pada koneksi - ini biasanya pendekatan yang lebih disukai. Dalam PHP:

    • Jika Anda menggunakan lapisan abstraksi PDO dengan PHP ≥ 5.3.6, Anda dapat menentukan charsetdi DSN :

      $dbh = new PDO('mysql:charset=utf8mb4');
    • Jika Anda menggunakan mysqli , Anda dapat menghubungi set_charset():

      $mysqli->set_charset('utf8mb4');       // object oriented style
      mysqli_set_charset($link, 'utf8mb4');  // procedural style
    • Jika Anda terjebak dengan mysql biasa tetapi kebetulan menjalankan PHP ≥ 5.2.3, Anda dapat menelepon mysql_set_charset.

  • Jika pengemudi tidak menyediakan mekanisme sendiri untuk menetapkan karakter koneksi set, Anda mungkin harus mengeluarkan permintaan untuk memberitahu MySQL bagaimana aplikasi Anda mengharapkan data pada koneksi yang akan dikodekan: SET NAMES 'utf8mb4'.

  • Pertimbangan yang sama tentang utf8mb4/ utf8berlaku seperti di atas.

Keluaran :

  • Jika aplikasi Anda mentransmisikan teks ke sistem lain, mereka juga perlu diberi tahu tentang pengkodean karakter. Dengan aplikasi web, browser harus diberi tahu tentang penyandian data yang dikirim (melalui header respons HTTP atau metadata HTML ).

  • Di PHP, Anda dapat menggunakan default_charsetopsi php.ini, atau menerbitkan Content-Typesendiri header MIME secara manual , yang hanya lebih berfungsi tetapi memiliki efek yang sama.

  • Saat menyandikan output menggunakan json_encode(), tambahkan JSON_UNESCAPED_UNICODEsebagai parameter kedua.

Masukan :

  • Sayangnya, Anda harus memverifikasi setiap string yang diterima sebagai UTF-8 yang valid sebelum Anda mencoba menyimpannya atau menggunakannya di mana saja. PHP mb_check_encoding()memang berhasil, tetapi Anda harus menggunakannya secara religius. Sebenarnya tidak ada jalan keluar, karena klien jahat dapat mengirimkan data dalam penyandian apa pun yang mereka inginkan, dan saya belum menemukan trik untuk membuat PHP melakukan ini untuk Anda secara andal.

  • Dari bacaan saya tentang spesifikasi HTML saat ini , sub-peluru berikut tidak diperlukan atau bahkan berlaku lagi untuk HTML modern. Pemahaman saya adalah bahwa browser akan bekerja dengan dan mengirimkan data dalam set karakter yang ditentukan untuk dokumen. Namun, jika Anda menargetkan versi HTML yang lebih lama (XHTML, HTML4, dll.), Poin-poin ini mungkin masih berguna:

    • Untuk HTML sebelum HTML5 saja : Anda ingin semua data yang dikirimkan kepada Anda oleh browser berada di UTF-8. Sayangnya, jika Anda pergi dengan satu-satunya cara untuk andal melakukan hal ini adalah menambahkan accept-charsetatribut untuk semua Anda <form>tag: <form ... accept-charset="UTF-8">.
    • Hanya untuk HTML sebelum HTML5 : perhatikan bahwa spesifikasi HTML W3C mengatakan bahwa klien "harus" default untuk mengirim formulir kembali ke server dalam charset apa pun yang dilayani server, tetapi ini tampaknya hanya sebuah rekomendasi, maka kebutuhan untuk menjadi eksplisit pada setiap satu <form>menandai.

Pertimbangan Kode Lainnya :

  • Cukup jelas, semua file yang akan Anda layani (PHP, HTML, JavaScript, dll.) Harus dikodekan dalam UTF-8 yang valid.

  • Anda perlu memastikan bahwa setiap kali Anda memproses string UTF-8, Anda melakukannya dengan aman. Sayangnya, ini adalah bagian yang sulit. Anda mungkin ingin memanfaatkan ekstensi PHP secara mbstringekstensif.

  • Operasi string bawaan PHP tidak secara default aman UTF-8. Ada beberapa hal yang dapat Anda lakukan dengan aman dengan operasi string PHP normal (seperti penggabungan), tetapi untuk sebagian besar hal Anda harus menggunakan mbstringfungsi yang setara .

  • Untuk mengetahui apa yang Anda lakukan (baca: bukan mengacaukannya), Anda benar-benar perlu tahu UTF-8 dan cara kerjanya pada level serendah mungkin. Lihatlah salah satu tautan dari utf8.com untuk mendapatkan sumber yang bagus untuk mempelajari semua yang perlu Anda ketahui.

chazomaticus
sumber
4
Ini pemahaman saya bahwa jika Anda menentukan collation sebagai utf8_ *, maka secara otomatis disandikan sebagai utf8. Apakah ini salah?
chazomaticus
49
Saya tidak salah: COLLATE menyiratkan KARAKTER SET. Lihat mis . Dev.mysql.com/doc/refman/5.0/id/charset-database.html .
chazomaticus
7
Pertimbangkan untuk menambahkan contoh PDO untuk mengatur set karakter juga.
Ja͢ck
97
Perhatikan bahwa MySQL tidak berbicara bahasa yang sama dengan orang lain. Ketika MySQL mengatakan "utf8" itu benar-benar berarti "beberapa varian UTF-8 yang terbelakang secara aneh yang terbatas pada tiga byte karena Tuhan tahu apa alasan konyolnya". Jika Anda benar-benar ingin UTF-8, Anda harus memberi tahu MySQL bahwa Anda menginginkan hal aneh ini yang suka dipanggil oleh MySQL utf8mb4 . Jangan repot-repot menabung di "WTF!"
R. Martinho Fernandes
4
Jawaban ini sangat membantu saya TETAPI saya juga menemukan bahwa dalam kasus saya, saya perlu menambahkan JSON_UNESCAPED_UNICODE ke PHP json_encode saya ketika meneruskan hasil permintaan DB kembali melalui ajax.
Petay87
150

Saya ingin menambahkan satu hal ke jawaban chazomaticus yang luar biasa :

Jangan lupa tag META (seperti ini, atau versi HTML4 atau XHTML ):

<meta charset="utf-8">

Tampaknya sepele, tetapi IE7 telah memberi saya masalah dengan itu sebelumnya.

Saya melakukan segalanya dengan benar; basis data, koneksi basis data, dan header HTTP Content-Type semuanya diatur ke UTF-8, dan itu berfungsi dengan baik di semua browser lain, tetapi Internet Explorer masih bersikeras menggunakan pengkodean "Eropa Eropa".

Ternyata halaman tersebut tidak memiliki tag META. Menambahkan itu memecahkan masalah.

Edit:

W3C sebenarnya memiliki bagian yang agak besar yang didedikasikan untuk I18N . Mereka memiliki sejumlah artikel yang berkaitan dengan masalah ini - menggambarkan sisi HTTP, (X) HTML dan CSS:

Mereka merekomendasikan penggunaan header HTTP dan meta tag HTML (atau deklarasi XML jika XHTML berfungsi sebagai XML).

mercator
sumber
Tidakkah seharusnya juga untuk menentukan charset di header HTTP? Mungkin memerlukan beberapa opsi konfigurasi untuk server web ...
oliver
2
@oliver: Ya, Anda dapat mengirimnya di tajuk HTTP, tetapi lebih baik mengirimnya di konten karena jika klien menyimpan file, itu akan selalu menyimpan meta tag. Header HTTP kemungkinan hilang begitu saja kecuali browser cukup pintar untuk menyalinnya ke tag meta di file yang disimpan.
5
Juga, pastikan baris itu adalah anak pertama dari elemen kepala (sebelum hal-hal Unicode). Browser dapat menafsirkan kembali halaman setelah mengenai elemen meta yang dijelaskan di atas.
alex
64

Selain pengaturan default_charsetdi php.ini, Anda dapat mengirim charset yang benar menggunakan header()dari dalam kode Anda, sebelum output apa pun:

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

Bekerja dengan Unicode dalam PHP itu mudah selama Anda menyadari bahwa sebagian besar fungsi string tidak bekerja dengan Unicode, dan beberapa mungkin membuat string sepenuhnya . PHP menganggap "karakter" panjangnya 1 byte. Terkadang ini baik-baik saja (misalnya, explode()hanya mencari urutan byte dan menggunakannya sebagai pemisah - jadi tidak masalah karakter aktual apa yang Anda cari). Tetapi di lain waktu, ketika fungsi sebenarnya dirancang untuk bekerja pada karakter , PHP tidak tahu bahwa teks Anda memiliki karakter multi-byte yang ditemukan dengan Unicode.

Pustaka yang bagus untuk diperiksa adalah phputf8 . Ini menulis ulang semua fungsi "buruk" sehingga Anda dapat bekerja dengan aman pada string UTF8. Ada ekstensi seperti ekstensi mbstring yang mencoba melakukan ini untuk Anda juga, tapi saya lebih suka menggunakan perpustakaan karena lebih portabel (tapi saya menulis produk pasar massal, jadi itu penting bagi saya). Tetapi phputf8 dapat menggunakan mbstring di belakang layar, untuk meningkatkan kinerja.

chroder
sumber
Atur pengaturan overload di php.ini. Ini membantu saat menggunakan string multi-byte.
Anthony Rutledge
32

Saya menemukan masalah dengan seseorang yang menggunakan PDO dan jawabannya adalah menggunakan ini untuk string koneksi PDO:

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

Situs tempat saya mengambil ini sedang down, tapi saya bisa mendapatkannya dengan menggunakan cache Google, untungnya.

Jim W.
sumber
1
Mencari ini sedikit lebih jauh, ini hanya diperlukan untuk versi PHP sebelum 5.3.6. Lihat juga: http://stackoverflow.com/a/4361485/2286722 (meskipun mereka menggunakan yang terpisah $dbh->exec("set names utf8");; saya lebih suka metode yang disajikan di sini). Btw. ada juga catatan serupa tentang ini sebagai komentar dalam manual PHP: php.net/manual/en/pdo.construct.php#96325 .
Marten Koetsier
24

Dalam kasus saya, saya menggunakan mb_split, yang menggunakan regex. Karena itu saya juga harus secara manual memastikan encoding regex adalah utf-8 dengan melakukanmb_regex_encoding('UTF-8');

Sebagai catatan, saya juga menemukan dengan menjalankan mb_internal_encoding()bahwa pengkodean internal tidak utf-8, dan saya mengubahnya dengan menjalankan mb_internal_encoding("UTF-8");.

JDelage
sumber
22

Pertama-tama jika Anda berada di <5.3PHP maka tidak. Anda punya banyak masalah untuk diatasi.

Saya terkejut bahwa tidak ada yang menyebutkan perpustakaan intl , yang memiliki dukungan yang baik untuk unicode , grapheme , operasi string , lokalisasi dan banyak lagi, lihat di bawah.

Saya akan mengutip beberapa informasi tentang dukungan unicode dalam PHP oleh slide Elizabeth Smith di PHPBenelux'14

INTL

Baik:

  • Membungkus perpustakaan ICU
  • Lokal terstandarisasi, set lokal per skrip
  • Pemformatan angka
  • Pemformatan mata uang
  • Pemformatan pesan (menggantikan gettext)
  • Kalender, tanggal, zona waktu dan waktu
  • Penerjemah
  • Spoofchecker
  • Bundel sumber daya
  • Konverter
  • Dukungan IDN
  • Grafem
  • Pemeriksaan
  • Iterator

Buruk:

  • Tidak mendukung zend_multibite
  • Tidak mendukung konversi keluaran input HTTP
  • Tidak mendukung fungsi yang berlebihan

mb_string

  • Mengaktifkan dukungan zend_multibyte
  • Mendukung penyandian masuk / keluar HTTP transparan
  • Menyediakan beberapa pembungkus untuk funtionallity seperti strtoupper

ICONV

  • Utama untuk konversi charset
  • Pegangan buffer output
  • fungsionalitas penyandian mime
  • konversi
  • beberapa string helper (len, substr, strpos, strrpos)
  • Filter Aliran stream_filter_append($fp, 'convert.iconv.ISO-2022-JP/EUC-JP')

DATABAS

  • mysql: Charset dan collation pada tabel dan koneksi (bukan collation). Juga jangan gunakan mysql - msqli atau PDO
  • postgresql: pg_set_client_encoding
  • sqlite (3): Pastikan itu dikompilasi dengan dukungan unicode dan intl

Beberapa Gotcha lainnya

  • Anda tidak dapat menggunakan nama file unicode dengan PHP dan windows kecuali jika Anda menggunakan ekstensi bagian ke-3.
  • Kirim semua yang ada di ASCII jika Anda menggunakan exec, proc_open dan panggilan baris perintah lainnya
  • Teks biasa bukan teks biasa, file memiliki penyandian
  • Anda dapat mengonversi file dengan cepat menggunakan filter ikonv

Saya akan memperbarui jawaban ini jika ada perubahan fitur ditambahkan dan sebagainya.

Jimmy Kane
sumber
2
Ya benar. Mysqli dan PDO dapat menggunakan driver asli mereka. Mereka juga dapat menggunakan driver mysqlnd jika Anda akan mengompilasi php dengan --with-mysqli=mysqlnd --with-pdo-mysql=mysqlndopsi.
Alexander Yancharuk
14

Satu-satunya hal yang saya tambahkan pada jawaban yang luar biasa ini adalah untuk menekankan pada menyimpan file Anda dalam pengkodean utf8, saya telah memperhatikan bahwa browser menerima properti ini lebih dari pengaturan utf8 sebagai pengkodean kode Anda. Setiap editor teks yang layak akan menunjukkan ini kepada Anda, misalnya Notepad ++ memiliki opsi menu untuk pengkondisian file, ini menunjukkan kepada Anda pengkodean saat ini dan memungkinkan Anda untuk mengubahnya. Untuk semua file php saya, saya menggunakan utf8 tanpa BOM.

Beberapa waktu yang lalu saya meminta seseorang untuk menambahkan dukungan utf8 untuk aplikasi php / mysql yang dirancang oleh orang lain, saya perhatikan bahwa semua file dikodekan dalam ANSI, jadi saya harus menggunakan ICONV untuk mengonversi semua file, mengubah tabel database untuk menggunakan utf8 charset dan utf8_general_ci collate, tambahkan 'SET NAMES utf8' ke lapisan abstraksi basis data setelah koneksi (jika menggunakan 5.3.6 atau sebelumnya, Anda harus menggunakan charset = utf8 dalam string koneksi) dan mengubah fungsi string untuk menggunakan multibyte php fungsi string setara.

Puerto AGP
sumber
13

Saya baru-baru ini menemukan bahwa menggunakan strtolower()dapat menyebabkan masalah di mana data terpotong setelah karakter khusus.

Solusinya adalah menggunakan

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

mb_ menggunakan MultiByte. Ini mendukung lebih banyak karakter tetapi secara umum sedikit lebih lambat.

Miguel Stevens
sumber
9

Saya baru saja mengalami masalah yang sama dan menemukan solusi yang baik di manual PHP.

Saya mengubah semua penyandian file saya ke UTF8 kemudian penyandian default pada koneksi saya. Ini menyelesaikan semua masalah.

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

Lihat sumber

Abdul Sadik Yalcin
sumber
2
Saya menghabiskan waktu satu jam untuk mencari tahu masalah pengkodean pada halaman yang saya kerjakan dan saya biasanya cukup pandai dalam mencari tahu hal-hal. Saya selalu berkonsultasi dengan halaman ini dan jawaban Anda banyak membantu saya. Mendapat upvote saya. Dalam kasus saya, set_charset('utf8mb4')tidak berfungsi tetapi >set_charset("utf8")berhasil dan itu tidak benar-benar ditampilkan dalam jawaban lain.
Funk Forty Niner
@FunkFortyNiner Awas: set_charset("utf8")mungkin berfungsi tetapi akan berperilaku berbeda (lihat komentar tentang perbedaan antara utf8dan utf8mb4dan riwayat versi mysql). Gunakan utf8 jika Anda harus DAN HANYA jika Anda tahu apa yang Anda lakukan !
Martin Hennings
Solusi 5 bintang, saya sedang membaca file teks baris demi baris dan dapatkan? untuk setiap karakter, maka saya melakukan save-as, bukannya ansi, menggunakan utf8. Terima kasih.
Atef Farouk
8

Di PHP, Anda harus menggunakan fungsi multibyte , atau mengaktifkan mbstring.func_overload . Dengan begitu hal-hal seperti strlen akan berfungsi jika Anda memiliki karakter yang membutuhkan lebih dari satu byte.

Anda juga perlu mengidentifikasi rangkaian karakter dari respons Anda. Anda bisa menggunakan AddDefaultCharset, seperti di atas, atau menulis kode PHP yang mengembalikan header. (Atau Anda dapat menambahkan tag META ke dokumen HTML Anda.)

JW.
sumber
Kiat hebat tentang pengaturan func_overload - memungkinkan modifikasi minimal ke kode yang ada.
Simon East
4
Berhati-hatilah - beberapa kode mungkin sebenarnya mengandalkan sifat satu byte per karakter dari fungsi string standar.
JW.
Penting untuk dicatat bahwa fitur mbstring.func_overload sedang tidak digunakan pada PHP 7.2, karena masalah yang dicatat dalam komentar @ JW di atas. Jadi saran terbaiknya adalah: Ya, Anda harus menggunakan fungsi mbstring, tetapi jangan menggunakan fitur kelebihan untuk mendapatkan fungsi standar berfungsi sebagai multibyte.
Simba
6

Dukungan Unicode di PHP masih sangat berantakan. Meskipun mampu mengubah string ISO8859 (yang digunakan secara internal) menjadi utf8, ia tidak memiliki kemampuan untuk bekerja dengan string unicode secara asli, yang berarti semua fungsi pemrosesan string akan memotong-motong dan merusak string Anda. Jadi, Anda harus menggunakan pustaka terpisah untuk dukungan utf8 yang tepat, atau menulis ulang sendiri semua fungsi penanganan string.

Bagian yang mudah hanya menentukan charset di header HTTP dan dalam database dan semacamnya, tetapi tidak ada yang penting jika kode PHP Anda tidak menampilkan UTF8 yang valid. Itu bagian yang sulit, dan PHP memberi Anda hampir tidak ada bantuan di sana. (Saya pikir PHP6 seharusnya memperbaiki yang terburuk dari ini, tapi itu masih beberapa saat lagi)

jalf
sumber
6

Jika Anda ingin server MySQL memutuskan set karakter, dan bukan PHP sebagai klien (perilaku lama; lebih disukai, menurut saya), coba tambahkan skip-character-set-client-handshakeke my.cnf, di bawah [mysqld], dan mulai ulang mysql.

Ini dapat menyebabkan masalah jika Anda menggunakan selain UTF8.

Budimir Grom
sumber
5

Jawaban teratas sangat bagus. Inilah yang harus saya lakukan pada setup debian / php / mysql reguler:

// 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

itu saja !

Commonpike
sumber
1

jika Anda menginginkan solusi mysql, saya memiliki masalah serupa dengan 2 proyek saya, setelah migrasi server. Setelah mencari dan mencoba banyak solusi saya menemukan ini / tidak ada sebelum ini bekerja):

mysqli_set_charset($con,"utf8");

Setelah menambahkan baris ini ke file konfigurasi saya semuanya berfungsi dengan baik!

Saya menemukan solusi ini https://www.w3schools.com/PHP/func_mysqli_set_charset.asp ketika saya sedang mencari untuk menyelesaikan penyisipan dari permintaan html

semoga berhasil!

castro_pereira
sumber
1

Hanya sebuah catatan:

Anda menghadapi masalah karakter non-latin Anda menunjukkan sebagai ?????????, Anda mengajukan pertanyaan, dan itu bisa ditutup dengan referensi untuk pertanyaan kanonik ini, Anda mencoba segalanya dan tidak peduli apa yang Anda lakukan Anda masih mendapatkan ??????????dari MySQL.

Itu sebagian besar karena Anda menguji data lama Anda yang telah dimasukkan ke database menggunakan charset yang salah dan dikonversi dan disimpan untuk benar-benar karakter tanda tanya ?. Yang berarti Anda kehilangan teks asli Anda selamanya dan apa pun yang Anda coba akan Anda dapatkan ???????.

sedang menerapkan apa yang telah Anda pelajari dari jawaban pertanyaan ini pada data baru dapat menyelesaikan masalah Anda.

Akuntan م
sumber
0

Saya punya masalah ini saat menampilkan tabel. Saya hanya menempatkan ini pada setiap variabel output gema:

<td><?php echo utf8_encode ($Local) ?></td>
Joao Fonseca
sumber