?
This document uses PHP Chinese website manual Release
本章是由 Alex Cabal 最初撰寫在 PHP Best Practices 中的,我們使用它作為進(jìn)行建議的基礎(chǔ)。
這不是在開玩笑。請(qǐng)小心、仔細(xì)并且前后一致地處理它。
目前,PHP 仍未在底層實(shí)現(xiàn)對(duì) Unicode 的支持。雖然有很多途徑可以確保 UTF-8 字符串能夠被正確地處理,但這并不是很簡(jiǎn)單的事情,通常需要對(duì) Web 應(yīng)用進(jìn)行全方面的檢查,從 HTML 到 SQL 再到 PHP。我們將爭(zhēng)取進(jìn)行一個(gè)簡(jiǎn)潔實(shí)用的總結(jié)。
PHP 層面的 UTF-8
最基本的字符串操作,像是連結(jié)兩個(gè)字符串或?qū)⒆址x值給變量,并不需要對(duì) UTF-8 做特別的處理。然而大多數(shù)字符串的函數(shù),像 strpos() 和 strlen(),確實(shí)需要特別的對(duì)待。這些函數(shù)通常都有一個(gè)冠以 mb_* 開頭的對(duì)等函數(shù):比如,mb_strpos() 和 mb_strlen()。這些 mb_* 開頭的字符串操作函數(shù)來源于 Multibyte String Extension,它專門為操作 Unicode 字符串而特別進(jìn)行了設(shè)計(jì)。
在操作 Unicode 字符串時(shí),請(qǐng)你務(wù)必使用 mb_* 函數(shù)。例如,如果你對(duì)一個(gè) UTF-8 字符串使用 substr(),那返回的結(jié)果中有很大可能會(huì)包含一些亂碼。正確的方式是使用 mb_substr()。
最難的地方在于每次都要記得使用 mb_* 函數(shù)。如果你哪怕只有一次忘記了使用,你的 Unicode 字符串就有在接下來的過程中變成亂碼的風(fēng)險(xiǎn)。
不是所有的字符串函數(shù)都有一個(gè)對(duì)應(yīng)的 mb_* 函數(shù)。如果你想要的功能沒有對(duì)應(yīng)的 mb_* 函數(shù)的話,那只能說你運(yùn)氣不佳了。
你應(yīng)該在你所有的 PHP 腳本(或全局包含的腳本)的開頭使用 mb_internal_encoding() 函數(shù),然后緊接著在會(huì)對(duì)瀏覽器進(jìn)行輸出的腳本中使用 mb_http_output()。在每一個(gè)腳本當(dāng)中明確聲明字符串的編碼可以免去很多日后的煩惱。
另外,許多對(duì)字符串進(jìn)行操作的函數(shù)都有一個(gè)可選的參數(shù)用來指定字符串編碼。當(dāng)可以設(shè)定這類參數(shù)時(shí),你應(yīng)該始終明確指定使用 UTF-8。例如,htmlentities() 有一個(gè)字符編碼的選項(xiàng),你應(yīng)該始終將其設(shè)為 UTF-8。從 PHP 5.4.0 開始, htmlentities() 和 htmlspecialchars() 的編碼都已經(jīng)被默認(rèn)設(shè)為了 UTF-8。
最后,如果你所編寫的是分布式的應(yīng)用程序并且不能確定 mbstring 擴(kuò)展一定開啟的話,可以考慮使用 patchwork/utf8 Composer 包。它會(huì)在 mbstring 可用時(shí)自動(dòng)使用,否則自動(dòng)切換回非 UTF-8 函數(shù)。
數(shù)據(jù)庫層面的 UTF-8
如果你使用 PHP 來操作到 MySQL,有些時(shí)候即使你做到了上面的每一點(diǎn),你的字符串仍可能面臨在數(shù)據(jù)庫中以非 UTF-8 的格式進(jìn)行存儲(chǔ)的問題。
為了確保你的字符串從 PHP 到 MySQL都使用 UTF-8,請(qǐng)檢查確認(rèn)你的數(shù)據(jù)庫和數(shù)據(jù)表都設(shè)定為 utf8mb4 字符集和整理,并且確保你的 PDO 連接請(qǐng)求也使用了 utf8mb4 字符集。請(qǐng)看下方的示例代碼,這是 非常重要 的。
請(qǐng)注意為了完整的 UTF-8 支持,你必須使用 utf8mb4 而不是 utf8!你會(huì)在進(jìn)一步閱讀中找到原因。
瀏覽器層面的 UTF-8
使用 mb_http_output() 函數(shù)來確保 PHP 向?yàn)g覽器輸出 UTF-8 格式的字符串。
隨后瀏覽器需要接收 HTTP 應(yīng)答來指定頁面是由 UTF-8 進(jìn)行編碼的。以前這一步是通過在頁面 <head> 標(biāo)簽下包含字符集 <meta> 標(biāo)簽實(shí)現(xiàn)的,這是一種可行的方式。但更好的做法是在 Content-Type 響應(yīng)頭中進(jìn)行設(shè)置,因?yàn)檫@樣做的速度會(huì)更快。
<?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 = 'êl síla erin l? e-govaned v?n.'; // Transform the string in some way with a multibyte function // Note how we cut the string at a non-Ascii character for demonstration purposes $string = mb_substr($string, 0, 15); // Connect to a database to store the transformed string // See the PDO example in this document for more information // Note the `charset=utf8mb4` in the Data Source Name (DSN) $link = new PDO( 'mysql:host=your-hostname; dbname=your-db; charset=utf8mb4', 'your-username', 'your-password', array( PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_PERSISTENT => false ) ); // Store our transformed string as UTF-8 in our database // Your DB and tables are in the utf8mb4 character set and collation, right? $handle = $link->prepare('insert into ElvishSentences (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 ElvishSentences 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); header('Content-Type: text/html; charset=UTF-8'); ?> <!doctype html> <html> <head> <meta 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>