## 沒有一行式解決方案。小心、注意細節,以及一致性。
PHP 中的 UTF-8 糟透了。原諒我的用詞。
目前 PHP 在低層次上還不支持 Unicode。有幾種方式可以確保 UTF-8 字符串能夠被正確處理, 但并不容易,需要深入到 web 應用的所有層面,從 HTML,到 SQL,到 PHP。我們旨在提供一個簡潔、 實用的概述。
## PHP 層面的 UTF-8
基本的[字符串操作](http://php.net/manual/zh/language.operators.string.php),如串接 兩個字符串、將字符串賦給變量,并不需要任何針對 UTF-8 的特殊東西。 然而,多數[字符串函數](http://php.net/manual/zh/ref.strings.php),如?[strpos()](http://php.net/manual/zh/function.strpos.php)?和?[strlen](http://php.net/manual/zh/function.strlen.php),就需要特殊的考慮。 這些函數都有一個對應的?`mb_*`?函數:例如,[mb_strpos()](http://php.net/manual/zh/function.mb-strpos.php)?和?[mb_strlen()](http://php.net/manual/zh/function.mb-strlen.php)。 這些對應的函數統稱為[多字節字符串函數](http://php.net/manual/zh/ref.mbstring.php)。 這些多字節字符串函數是專門為操作 Unicode 字符串而設計的。
當你操作 Unicode 字符串時,必須使用?`mb_*`?函數。 例如,如果你使用?[substr()](http://php.net/manual/zh/function.substr.php)?操作一個 UTF-8 字符串,其結果就很可能包含一些亂碼。 正確的函數應該是對應的多字節函數,?[mb_substr()](http://php.net/manual/zh/function.mb-substr.php)。
難的是始終記得使用?`mb_*`?函數。即使你僅一次忘了,你的 Unicode 字符串在接下來的處理中就可能產生亂碼。
并不是所有的字符串函數都有一個對應的?`mb_*`。如果不存在你想要的那一個,那你就只能自認倒霉了。
此外,在每個 PHP 腳本的頂部(或者在全局包含腳本的頂部)你都應使用?[mb_internal_encoding](http://php.net/manual/zh/function.mb-internal-encoding.php)?函數,如果你的腳本會輸出到瀏覽器,那么還得緊跟其后加個[mb_http_output()](http://php.net/manual/zh/function.mb-http-output.php)?函數。在每個腳本中顯式地定義字符串的編碼在以后能為你減少很多令人頭疼的事情。
最后,許多操作字符串的 PHP 函數都有一個可選參數讓你指定字符編碼。 若有該選項, 你應始終顯式地指明 UTF-8 編碼。 例如,[htmlentities()](http://php.net/manual/zh/function.htmlentities.php)?就有一個字符編碼方式選項,在處理這樣的字符串時應始終指定 UTF-8。
## MySQL 層面的 UTF-8
如果你的 PHP 腳本會訪問 MySQL,即使你遵從了前述的注意事項,你的字符串也有可能在數據庫中存儲為非 UTF-8 字符串。
確保從 PHP 到 MySQL 的字符串為 UTF-8 編碼的,確保你的數據庫以及數據表均設置為 utf8mb4 字符集, 并且在你的數據庫中執行任何其他查詢之前先執行 MySQL 查詢 `set names utf8mb4`。這是至關重要的。 示例請查看[連接并查詢 MySQL 數據庫](http://phpbestpractices.justjavac.com/#mysql)一節內容。
注意你必須使用 `utf8mb4` 字符集來獲得完整的 UTF-8 支持,而不是 `utf8` 字符集!原因請查看[進一步閱讀](http://phpbestpractices.justjavac.com/#utf8-further-reading)。
## 瀏覽器層面的 UTF-8
使用?[mb_http_output()](http://php.net/manual/zh/function.mb-http-output.php)?函數 來確保你的 PHP 腳本輸出 UTF-8 字符串到瀏覽器。 并且在 HTML 頁面的?`<head> `?標簽塊中包含?[字符集?` <meta> `?標簽塊](http://htmlpurifier.org/docs/enduser-utf8.html)。
## 示例
~~~
<?php
// Tell PHP that we're using UTF-8 strings until the end of the script
mb_internal_encoding('UTF-8');
// Tell PHP that we'll be outputting UTF-8 to the browser
mb_http_output('UTF-8');
// Our UTF-8 test string
$string = 'A? galiu valgyti stikl? ir jis man?s ne?eid?ia';
// Transform the string in some way with a multibyte function
$string = mb_substr($string, 0, 10);
// Connect to a database to store the transformed string
// See the PDO example in this document for more information
// Note the `set names utf8mb4` commmand!
$link = new \PDO( 'mysql:host=your-hostname;dbname=your-db',
'your-username',
'your-password',
array(
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::ATTR_PERSISTENT => false,
\PDO::MYSQL_ATTR_INIT_COMMAND => 'set names utf8mb4'
)
);
// Store our transformed string as UTF-8 in our database
// Assume our DB and tables are in the utf8mb4 character set and collation
$handle = $link->prepare('insert into Sentences (Id, Body) values (?, ?)');
$handle->bindValue(1, 1, PDO::PARAM_INT);
$handle->bindValue(2, $string);
$handle->execute();
// Retrieve the string we just stored to prove it was stored correctly
$handle = $link->prepare('select * from Sentences where Id = ?');
$handle->bindValue(1, 1, PDO::PARAM_INT);
$handle->execute();
// Store the result into an object that we'll output later in our HTML
$result = $handle->fetchAll(\PDO::FETCH_OBJ);
?><!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>UTF-8 test page</title>
</head>
<body>
<?php
foreach($result as $row){
print($row->Body); // This should correctly output our transformed UTF-8 string to the browser
}
?>
</body>
</html>
~~~
## 進一步閱讀
* [PHP 手冊:多字節字符串函數](http://php.net/manual/zh/ref.mbstring.php)
* [PHP UTF-8 備忘單](http://blog.loftdigital.com/blog/php-utf-8-cheatsheet)
* [Stack Overflow: 什么因素致使 PHP 不兼容 Unicode?](http://stackoverflow.com/questions/571694/what-factors-make-php-unicode-incompatible)
* [Stack Overflow: PHP 與 MySQL 之間國際化字符串的最佳實踐](http://stackoverflow.com/questions/140728/best-practices-in-php-and-mysql-with-international-strings)
* [怎樣在MySQL數據庫中完整支持Unicode](http://mathiasbynens.be/notes/mysql-utf8mb4)