最近看了不少編碼方面的文章,所以分二篇博文說下“PHP、字符串、編碼、UTF-8”相關知識,本篇博文是上半部分,分為四大塊內容,分別是“字符串的定義和使用”、“字符串轉換”、“PHP 字符串的本質”、“多字節字符串”。上半部分比較基礎,下一篇文章《PHP 與 UTF-8的最佳實踐》可能干貨更多一點。
字符串的定義和使用
PHP 中能夠通過四種方法設置字符串:
單引號字符串
單引號字符串
類似于 Python 中的原始字符串,也就是說單引號字符串
沒有變量解析功能和特殊字符轉義功能。比如$str='hello\nworld'
,其中的\n
并沒有換行功能。
雙引號字符串
雙引號字符串
具備單引號字符串
沒有的變量解析功能和特殊字符轉義功能。
個人對于十六進制和八進制的字符串特殊轉義很感興趣,特別補充:
\[0-7]{1,3} #八進制表達方式
\x[0-9A-Fa-f]{1,2} #十六進制表達方式
heredoc
這種表達式類似于 Python 中的長字符串,能夠定義包含多行的字符串。其語法定義很嚴格,使用起來需要注意。
$str=<<<EOD
hello\n
world
EOD;
Nowdoc
Nowdoc類似于單引號字符串,不會解析變量。比較適合定義一大段文本且無需對其中的特殊字符進行轉義。
變量解析
PHP字符串最強大的部分就是變量解析,可以在運行時根據上下文解析變量(這才是解釋型語言),可以產生很多妙用。
簡單的變量解析就是在字符串中可以包含“變量”,“數組”,“對象屬性”,復雜的語法規則就是使用{}
符號來進行操作(組成一個表達式)。
通過一個例子看看變量解析的強大之處
class beers {
const softdrink = 'softdrink';
public static $ale = 'ale';
public $data = array(1,3,"k"=>4);
}
$softdrink = "softdrink";
$ale = "ale";
$arr = array("arr1","arr2","arr3"=>"arr4","arr4"=>array(1,2));
$arr4 = "arr4";
$obj = new beers;
echo "line1:{$arr[1]}\n";
echo "line2:{$arr['arr4'][0]}\n";
echo "line3:{$obj->data[1]}\n";
echo "line4:{${$arr['arr3']}}\n";
echo "line5:{${$arr['arr3']}[1]}\n";
echo "line6:{${beers::softdrink}}\n";
echo "line7:{${beers::$ale}}\n";
字符串轉換
PHP 語言比 Python 簡單的另外一個原因就是類型的隱式轉換,會簡化很多操作,這里通過字符串轉換來說明。
字符串類型強制轉換
$var = 10 ;
$dvar = (string)$var ;
echo $dvar . "_" . gettype($dvar);
strval()函數是獲取變量的字符串值:
$var = 10.2 ;
$dvar = strval($var) ;
echo gettype($var) . "_" . $dvar . "_" . gettype($dvar);
settype()函數是設置變量的類型:
$str = "10hello";
settype($str, "integer");
echo $str ;
在強制類型轉換過程中,將其他類型的值轉換為字符串的時候會遵循一定的規則,比如一個布爾值 boolean 的 TRUE 被轉換成 string 的 “1”。相關規則最好還是理解下。
自動類型轉換
上面的二個轉換屬于顯示轉換,而更要關注的是自動類型轉換,
在一個需要字符串的表達式中,會自動轉換為類型,具體見例子:
$bool = true;
$str = 10 + "hello"
echo $bool . "_" . $str ;
PHP 字符串的本質
引用 PHP 文檔的解釋:
PHP 中的 string 的實現方式是一個由字節組成的數組再加上一個整數指明緩沖區長度。并無如何將字節轉換成字符的信息,由程序員來決定。字符串由什么值構成沒有限制,包括值為 0 的字節可以出現在字符串的任何位置。
PHP并不特別指明字符串的編碼,那字符串到底是怎樣編碼的呢,這取決于程序員。字符串會按照 PHP 文件的編碼來對字符串進行編碼。比如你的文件編碼是 GBK,那么你代碼內容都是 GBK 的。
補充二進制安全這個概念,其值為 0 (NULL)的字節可以處于字符串任何位置,而 PHP 的部分非二進制函數底層是調用的 C 函數,會把 NULL 后面的字符忽略。
只要 PHP 的文件編碼是能兼容 ASCII 的,那么字符串操作就可以很好的被處理。但是字符串操作本質上還是 Native 的(不管文件編碼是什么),所以在使用的時候需要注意:
- 某些函數假定字符串是以單字節編碼的,但并不需要將字節解釋為特定的字符。比如 sbustr() 函數。
- 很多函數是需要顯示的傳遞編碼參數,不然會從 PHP.INI 文件中獲取默認值,比如 htmlentities() 函數。
- 還有一些函數和本地區域有關,這些函數也只能是單字節操作的。
一般情況下,雖然 PHP 內部不支持 Unicode 字符,但是支持 UTF-8 編碼,絕大部分情況下不會有什么問題,但是下列的情況可能就處理不了了:
- 非 UTF-8 編碼字符串如何進行轉換
- 一個 UTF-8 編碼的網頁,但是用戶在提交表單的時候,可能使用 GBK 的編碼(不遵守 meta tag)
- 一個 UTF-8 編碼的 PHP 文件,使用 strlen("中國") 返回的是 6,而不是實際的字符數(2)
那么如何解決該問題呢? PHP 提供了 mbstring 擴展 !
多字節字符串
mbstring 擴展默認不是打開的,安裝的時候需要 --enable-mbstring。
我們首先看看 PHP.INI 中對于 mbstring 指令的配置,花了好久才逐步明白。
- mbstring.language 這個參數我就理解為 UTF-8 了
- mbstring.internal_encoding 這個編碼和 PHP 文件編碼沒有關系,只是在大部分 mbstring 函數里面需要指定待處理字符串的編碼,假如不顯示指定,默認就獲取該參數的值,該參數的值在高版本 PHP 中用 default_charset 參數代替了。
- mbstring.http_input 該參數指定 HTTP input 的默認編碼(不包含 GET 參數)。一般和 HTML 頁面的編碼保持一致,該參數的值用 default_charset 參數代替。
- mbstring.http_output 該參數誤導我了,HTTP output 是什么,PHP 輸出不就是頁面,怎么會有這概念?
- mbstring.encoding_translation,這個參數重點說下,默認是關閉的,假如打開,PHP 會對 POST 變量和上傳文件的名稱自動轉換編碼為 mbstring.internal_encoding 指定的值,不過我沒有試驗過,大家可以上傳一個中文名的文件試驗下。建議關閉,讓程序員來處理相關問題。
后面看看 mbstring 擴展的一些函數:
- mb_http_input():檢測 HTTP input 字符編碼,覺得對于文件上傳的文件名有必要處理。
- mb_convert_encoding():比較常用的函數,注意第三個參數。
- mb_detect_order():設置/獲取字符編碼的檢測順序。
- mb_list_encodings():返回系統支持的編碼列表。
重點說明下:PHP 文件支持的編碼有一定要,要兼容 ASCII。
但是不要使用 BIG-5 作為 PHP 文件編碼,尤其字符串以 identifiers 或 literals 形式出現,假如 PHP 文件編碼一定要是 BIG-5,那么對于輸入輸出的內容盡量轉換為 UTF-8。
Zend Multibyte
最后說下 Zend Multibyte 這個概念,理解的不是特別深刻,首先不要和 mbstring 擴展混在一塊。 Zend Multibyte 模式默認是關閉的,可以通過 zend.multibyte 指令打開。然后通過 declare() 函數來指定 PHP 解析器的編碼。
那這個指令出現的意義是什么?上面說過 PHP 文件的編碼需要是兼容 ASCII 的,那么類似于 BIG-5 這樣的非兼容 ASCII 編碼怎么辦,可以通過這個指令來操作,當 PHP 解析器讀取 mbstring.script_encoding 編碼并用該編碼來解析 PHP 文件。